Extending
Adding API Options

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:

  1. Test Thoroughly - Test with all framework and feature combinations
  2. Update Documentation - Add API-specific guides and examples
  3. Community Feedback - Gather feedback from early adopters
  4. Performance Optimization - Optimize for bundle size and runtime performance

Related Guides:

API Ecosystem: Your API integration expands React Kickstart's data fetching capabilities and provides developers with modern, efficient options for their applications.