Adding a New API Option
Comprehensive guide to extending React Kickstart's API client choices by adding new HTTP clients and data fetching solutions (e.g., Fetch, Axios, React Query, SWR, ky, etc.).
Current Options: React Kickstart currently supports Axios, Fetch, React Query combinations, and none. This guide shows how to add new options.
๐ฏ Integration Architecture
๐ Complete Integration Guide
Declare API Dependencies
File: src/builders/dependencies.js
Add your API client dependencies and versions:
// Add ky HTTP client dependencies
export const api = {
// ...existing API dependencies
ky: "^1.0.0",
kyUniversal: "^0.11.0" // for SSR compatibility
}
export function getKyDependencies(framework) {
const deps = {
ky: api.ky
}
// Add universal version for SSR frameworks
if (framework === 'nextjs') {
deps['ky-universal'] = api.kyUniversal
}
return deps
}
Wire Dependency Resolution
File: src/builders/dependency-resolver.js
Update the API dependency resolution:
getApiDependencies(apiChoice, framework) {
switch (apiChoice) {
case 'axios':
return getAxiosDependencies()
case 'fetch':
return {} // Native fetch, no dependencies
case 'axios-react-query':
return { ...getAxiosDependencies(), ...getReactQueryDependencies() }
case 'fetch-react-query':
return getReactQueryDependencies()
case 'ky':
return getKyDependencies(framework)
case 'swr':
return getSWRDependencies()
case 'apollo':
return getApolloClientDependencies()
case 'none':
default:
return {}
}
}
// Handle framework-specific API considerations
handleApiFrameworkIntegration(apiChoice, framework) {
const integrations = {}
if (framework === 'nextjs') {
switch (apiChoice) {
case 'ky':
// Use ky-universal for SSR compatibility
integrations.serverSideImport = 'ky-universal'
break
case 'apollo':
// Apollo needs SSR configuration
integrations.ssrConfig = true
break
}
}
return integrations
}
Create Feature Setup Module
File: src/features/api-clients/your-api-setup.js
Create a comprehensive setup module:
// src/features/api-clients/ky-setup.js
import { CORE_UTILS } from '../../utils/index.js'
import path from 'path'
export class KySetup {
async setup(projectPath, userChoices) {
await this.createKyClient(projectPath, userChoices)
await this.createApiUtils(projectPath, userChoices)
await this.createExampleUsage(projectPath, userChoices)
}
async createKyClient(projectPath, userChoices) {
const { framework, typescript } = userChoices
const clientContent = `
${framework === 'nextjs' ? "import ky from 'ky-universal'" : "import ky from 'ky'"}
${typescript ? 'import type { Options } from ky' : ''}
// Create ky instance with base configuration
export const apiClient = ky.create({
prefixUrl: process.env.${framework === 'nextjs' ? 'NEXT_PUBLIC_' : ''}REACT_APP_API_URL || 'http://localhost:3001',
timeout: 10000,
retry: {
limit: 2,
methods: ['get'],
statusCodes: [408, 413, 429, 500, 502, 503, 504]
},
hooks: {
beforeRequest: [
(request) => {
// Add auth token if available
const token = ${framework === 'nextjs' ? 'typeof window !== "undefined" ? localStorage.getItem("token") : null' : 'localStorage.getItem("token")'}
if (token) {
request.headers.set('Authorization', \`Bearer \${token}\`)
}
}
],
afterResponse: [
async (request, options, response) => {
// Handle global response logic
if (response.status === 401) {
// Handle unauthorized
${framework === 'nextjs' ? 'if (typeof window !== "undefined") localStorage.removeItem("token")' : 'localStorage.removeItem("token")'}
}
return response
}
]
}
})
// Typed API methods
export const api = {
get: ${typescript ? '<T = any>' : ''}(endpoint${typescript ? ': string' : ''}) =>
apiClient.get(endpoint).json${typescript ? '<T>' : ''}(),
post: ${typescript ? '<T = any>' : ''}(endpoint${typescript ? ': string' : ''}, data${typescript ? ': any' : ''}) =>
apiClient.post(endpoint, { json: data }).json${typescript ? '<T>' : ''}(),
put: ${typescript ? '<T = any>' : ''}(endpoint${typescript ? ': string' : ''}, data${typescript ? ': any' : ''}) =>
apiClient.put(endpoint, { json: data }).json${typescript ? '<T>' : ''}(),
delete: ${typescript ? '<T = any>' : ''}(endpoint${typescript ? ': string' : ''}) =>
apiClient.delete(endpoint).json${typescript ? '<T>' : ''}(),
// File upload helper
upload: ${typescript ? '<T = any>' : ''}(endpoint${typescript ? ': string' : ''}, formData${typescript ? ': FormData' : ''}) =>
apiClient.post(endpoint, { body: formData }).json${typescript ? '<T>' : ''}()
}
`
const clientPath = path.join(projectPath, 'src', 'lib', `api.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.ensureDirectory(path.dirname(clientPath))
await CORE_UTILS.writeFile(clientPath, clientContent.trim())
}
async createApiUtils(projectPath, userChoices) {
const { typescript } = userChoices
const utilsContent = `
import { api } from './api'
${typescript ? `
export interface ApiError {
message: string
status: number
code?: string
}
export interface PaginatedResponse<T> {
data: T[]
total: number
page: number
limit: number
}
` : ''}
// Error handling utility
export async function handleApiCall${typescript ? '<T>' : ''}(
apiCall${typescript ? ': () => Promise<T>' : ''}
)${typescript ? ': Promise<{ data: T | null; error: ApiError | null }>' : ''} {
try {
const data = await apiCall()
return { data, error: null }
} catch (error) {
const apiError${typescript ? ': ApiError' : ''} = {
message: error.message || 'An error occurred',
status: error.response?.status || 500,
code: error.response?.data?.code
}
return { data: null, error: apiError }
}
}
// Common API calls
export const userApi = {
getProfile: () => api.get('/user/profile'),
updateProfile: (data${typescript ? ': any' : ''}) => api.put('/user/profile', data),
getUsers: (params${typescript ? ': { page?: number; limit?: number }' : ''} = {}) =>
api.get(\`/users?\${new URLSearchParams(params).toString()}\`)
}
`
const utilsPath = path.join(projectPath, 'src', 'lib', `api-utils.${typescript ? 'ts' : 'js'}`)
await CORE_UTILS.writeFile(utilsPath, utilsContent.trim())
}
async createExampleUsage(projectPath, userChoices) {
const { typescript } = userChoices
const exampleContent = `
import React, { useState, useEffect } from 'react'
import { api, handleApiCall } from '../lib/api-utils'
${typescript ? `
interface User {
id: number
name: string
email: string
}
` : ''}
export function ApiExample() {
const [users, setUsers] = useState${typescript ? '<User[]>' : ''}([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState${typescript ? '<string | null>' : ''}(null)
const fetchUsers = async () => {
setLoading(true)
setError(null)
const { data, error: apiError } = await handleApiCall(() =>
api.get('/users')
)
if (apiError) {
setError(apiError.message)
} else {
setUsers(data || [])
}
setLoading(false)
}
useEffect(() => {
fetchUsers()
}, [])
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error}</div>
return (
<div>
<h2>Users (ky API Client)</h2>
<button onClick={fetchUsers}>Refresh</button>
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
)
}
`
const examplePath = path.join(projectPath, 'src', 'components', `ApiExample.${typescript ? 'tsx' : 'jsx'}`)
await CORE_UTILS.ensureDirectory(path.dirname(examplePath))
await CORE_UTILS.writeFile(examplePath, exampleContent.trim())
}
}
Update Prompt System
File: src/prompts/steps/api-step.js
Add your API option to the choices:
getChoices(previousAnswers) {
return [
{
name: 'Axios (Popular HTTP client)',
value: 'axios',
short: 'Axios'
},
{
name: 'Fetch (Native browser API)',
value: 'fetch',
short: 'Fetch'
},
{
name: 'Axios + React Query (HTTP client + caching)',
value: 'axios-react-query',
short: 'Axios + React Query'
},
{
name: 'Fetch + React Query (Native + caching)',
value: 'fetch-react-query',
short: 'Fetch + React Query'
},
{
name: 'ky (Tiny & elegant HTTP client)',
value: 'ky',
short: 'ky'
},
{
name: 'SWR (Data fetching with caching)',
value: 'swr',
short: 'SWR'
},
{
name: 'Apollo Client (GraphQL client)',
value: 'apollo',
short: 'Apollo'
},
{
name: 'None (No HTTP client setup)',
value: 'none',
short: 'None'
}
]
}
getRecommendation(previousAnswers) {
// Provide intelligent recommendations
if (previousAnswers.state === 'redux') {
return {
choice: 'axios-react-query',
reason: 'React Query pairs well with Redux for server state management'
}
}
if (previousAnswers.framework === 'nextjs') {
return {
choice: 'swr',
reason: 'SWR is built by Vercel and works excellently with Next.js'
}
}
return {
choice: 'fetch-react-query',
reason: 'Modern approach with native fetch and powerful caching'
}
}
Integrate with Feature System
File: src/features/api-clients/index.js
Update the main API client integration:
import { AxiosSetup } from "./axios-setup.js";
import { FetchSetup } from "./fetch-setup.js";
import { ReactQuerySetup } from "./react-query-setup.js";
import { KySetup } from "./ky-setup.js";
import { SWRSetup } from "./swr-setup.js";
import { ApolloSetup } from "./apollo-setup.js";
export async function setupApiClient(projectPath, userChoices) {
const { api } = userChoices;
switch (api) {
case "axios":
const axiosSetup = new AxiosSetup();
await axiosSetup.setup(projectPath, userChoices);
break;
case "fetch":
const fetchSetup = new FetchSetup();
await fetchSetup.setup(projectPath, userChoices);
break;
case "axios-react-query":
const axiosRQSetup = new AxiosSetup();
const reactQuerySetup = new ReactQuerySetup();
await axiosRQSetup.setup(projectPath, userChoices);
await reactQuerySetup.setup(projectPath, userChoices);
break;
case "fetch-react-query":
const fetchRQSetup = new FetchSetup();
const reactQuerySetup2 = new ReactQuerySetup();
await fetchRQSetup.setup(projectPath, userChoices);
await reactQuerySetup2.setup(projectPath, userChoices);
break;
case "ky":
const kySetup = new KySetup();
await kySetup.setup(projectPath, userChoices);
break;
case "swr":
const swrSetup = new SWRSetup();
await swrSetup.setup(projectPath, userChoices);
break;
case "apollo":
const apolloSetup = new ApolloSetup();
await apolloSetup.setup(projectPath, userChoices);
break;
case "none":
default:
// No API client setup
break;
}
}
export function getApiInfo(userChoices) {
const { api } = userChoices;
const apiInfo = {
ky: {
description: "Tiny & elegant HTTP client based on Fetch API",
features: [
"Promise-based",
"Request/Response hooks",
"Retry logic",
"TypeScript support",
],
bundleSize: "~4kb",
},
swr: {
description: "Data fetching library with focus on performance",
features: ["Caching", "Revalidation", "Real-time", "TypeScript support"],
bundleSize: "~5kb",
},
apollo: {
description: "Comprehensive GraphQL client with caching",
features: ["GraphQL", "Caching", "Subscriptions", "DevTools"],
bundleSize: "~33kb",
},
};
return apiInfo[api] || null;
}
Update Template System
File: src/templates/frameworks/vite/content-generator.js
Update templates to integrate API examples:
generateAppComponent(userChoices) {
const { api, typescript } = userChoices
let imports = [`import React from 'react'`]
let providerWrapper = null
let exampleComponent = null
switch (api) {
case 'swr':
imports.push(`import { SWRConfig } from 'swr'`)
imports.push(`import { swrConfig } from './lib/swr-config'`)
imports.push(`import { ApiExample } from './components/ApiExample'`)
providerWrapper = 'SWRConfig'
exampleComponent = 'ApiExample'
break
case 'apollo':
imports.push(`import { ApolloProvider } from '@apollo/client'`)
imports.push(`import { apolloClient } from './lib/apollo-client'`)
imports.push(`import { GraphQLExample } from './components/GraphQLExample'`)
providerWrapper = 'ApolloProvider'
exampleComponent = 'GraphQLExample'
break
case 'ky':
case 'axios':
case 'fetch':
imports.push(`import { ApiExample } from './components/ApiExample'`)
exampleComponent = 'ApiExample'
break
}
const providerProps = {
'SWRConfig': 'value={swrConfig}',
'ApolloProvider': 'client={apolloClient}'
}
return `${imports.join('\n')}
function App() {
return (
${providerWrapper ? `<${providerWrapper} ${providerProps[providerWrapper]}>` : ''}
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900 mb-4">
Welcome to React Kickstart!
</h1>
<p className="text-lg text-gray-600 mb-8">
Your React app with ${api} is ready to go.
</p>
${exampleComponent ? `<${exampleComponent} />` : ''}
</div>
</div>
${providerWrapper ? `</${providerWrapper}>` : ''}
)
}
export default App`
}
Add to QA System
File: qa-automation/test-matrix-generator.js
Add your API option to the test matrix:
const CONFIG_OPTIONS = {
framework: ["vite", "nextjs"],
typescript: [true, false],
api: [
"axios",
"fetch",
"axios-react-query",
"fetch-react-query",
"ky",
"swr",
"apollo",
"none",
],
// ... other options
};
// Add API-specific validation
const API_VALIDATION = {
ky: {
requiredFiles: ["src/lib/api.ts", "src/lib/api-utils.ts"],
requiredDependencies: ["ky"],
},
swr: {
requiredFiles: ["src/lib/swr-config.ts", "src/hooks/api-hooks.ts"],
requiredDependencies: ["swr"],
},
apollo: {
requiredFiles: ["src/lib/apollo-client.ts", "src/graphql/queries.ts"],
requiredDependencies: ["@apollo/client", "graphql"],
},
};
๐งช Testing Your Integration
Manual Testing
Test your API option with different combinations:
# Test with Vite + TypeScript
node bin/react-kickstart.js test-ky --yes --framework vite --api ky --typescript
# Test with Next.js
node bin/react-kickstart.js test-swr --yes --framework nextjs --api swr
# Test with state management
node bin/react-kickstart.js test-apollo --yes --framework vite --api apollo --state redux --typescript
Integration Testing
Verify the generated project works:
cd test-ky
npm install
npm run dev # Should start without errors
npm run build # Should build successfully
QA Automation
Run comprehensive tests:
# Generate updated test matrix
node qa-automation/test-matrix-generator.js
# Run critical tests
node qa-automation/test-runner.js critical --full
๐ Best Practices
API Client Design
Consistent Interface: Provide a consistent API interface regardless of the underlying HTTP client to make switching easier.
- Error Handling - Implement consistent error handling patterns
- TypeScript Support - Provide full TypeScript definitions and type safety
- Authentication - Include token-based auth patterns
- Interceptors - Set up request/response interceptors for common logic
- Environment Configuration - Support different API URLs for different environments
Framework Compatibility
- SSR Considerations - Handle server-side rendering requirements (Next.js)
- Bundle Size - Consider the impact on bundle size
- Tree Shaking - Ensure proper tree shaking support
- Polyfills - Include necessary polyfills for older browsers
Developer Experience
- Documentation - Provide clear usage examples and API documentation
- DevTools Integration - Support browser DevTools and debugging
- Hot Reload - Ensure compatibility with development hot reload
- Testing Utilities - Provide utilities for testing API calls
๐ Next Steps
After Integration:
- Test Thoroughly - Test with all framework and feature combinations
- Update Documentation - Add API-specific guides and examples
- Community Feedback - Gather feedback from early adopters
- Performance Optimization - Optimize for bundle size and runtime performance
Related Guides:
- Adding Features โ - General feature addition guide
- Architecture Overview โ - Understanding the system design
- Contributing โ - Contribution guidelines and workflow
API Ecosystem: Your API integration expands React Kickstart's data fetching capabilities and provides developers with modern, efficient options for their applications.