API Integration Guide for v0 Applications
14 min readBy Sarah Chen
Tutorial

API Integration Guide for v0 Applications

Connect your v0 applications to external APIs and services effectively.

Modern applications rely on APIs for data and functionality. Learn how to integrate external APIs effectively in v0 projects with proper error handling, caching, and security.

API Types

Understanding different API architectures helps you choose the right approach:

REST APIs: Traditional HTTP-based APIs using standard methods (GET, POST, PUT, DELETE). Best for simple CRUD operations and widely supported.

GraphQL: Query language for APIs allowing clients to request exactly the data they need. Ideal for complex data requirements and reducing over-fetching.

WebSockets: Bidirectional communication for real-time features. Perfect for chat applications, live updates, and collaborative tools.

Server-Sent Events (SSE): One-way communication from server to client. Great for live notifications and streaming data.

Making API Calls

Use modern approaches for reliable API communication:

Fetch API with Error Handling:

```typescript

// lib/api/client.ts

export class APIError extends Error {

constructor(

message: string,

public status: number,

public data?: any

) {

super(message)

this.name = 'APIError'

}

}

export async function fetchAPI<T>(

endpoint: string,

options?: RequestInit

): Promise<T> {

const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}${endpoint}`, {

...options,

headers: {

'Content-Type': 'application/json',

...options?.headers,

},

})

if (!response.ok) {

const error = await response.json().catch(() => ({}))

throw new APIError(

error.message || 'API request failed',

response.status,

error

)

}

return response.json()

}

```

SWR for Data Fetching:

```typescript

import useSWR from 'swr'

import { fetchAPI } from '@/lib/api/client'

export function useUser(id: string) {

const { data, error, isLoading, mutate } = useSWR(

`/users/${id}`,

fetchAPI,

{

revalidateOnFocus: false,

revalidateOnReconnect: true,

dedupingInterval: 5000,

onError: (error) => {

console.error('Failed to fetch user:', error)

},

}

)

return {

user: data,

isLoading,

isError: error,

mutate, // Manually revalidate

}

}

// Usage in component

function UserProfile({ userId }: { userId: string }) {

const { user, isLoading, isError } = useUser(userId)

if (isLoading) return <Skeleton />

if (isError) return <ErrorMessage />

return <Profile user={user} />

}

```

React Query for Advanced Caching:

```typescript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

export function usePosts() {

return useQuery({

queryKey: ['posts'],

queryFn: () => fetchAPI('/posts'),

staleTime: 5 * 60 * 1000, // 5 minutes

cacheTime: 10 * 60 * 1000, // 10 minutes

})

}

export function useCreatePost() {

const queryClient = useQueryClient()

return useMutation({

mutationFn: (newPost: Post) => fetchAPI('/posts', {

method: 'POST',

body: JSON.stringify(newPost),

}),

onSuccess: () => {

// Invalidate and refetch

queryClient.invalidateQueries({ queryKey: ['posts'] })

},

})

}

```

Server-Side API Calls

Leverage Next.js features for secure, efficient API calls:

API Routes:

```typescript

// app/api/posts/route.ts

import { NextRequest, NextResponse } from 'next/server'

export async function GET(request: NextRequest) {

try {

const searchParams = request.nextUrl.searchParams

const page = searchParams.get('page') || '1'

const response = await fetch(`https://api.example.com/posts?page=${page}`, {

headers: {

'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,

},

next: { revalidate: 60 }, // Cache for 60 seconds

})

if (!response.ok) {

throw new Error('Failed to fetch posts')

}

const data = await response.json()

return NextResponse.json(data)

} catch (error) {

return NextResponse.json(

{ error: 'Failed to fetch posts' },

{ status: 500 }

)

}

}

export async function POST(request: NextRequest) {

try {

const body = await request.json()

// Validate input

if (!body.title || !body.content) {

return NextResponse.json(

{ error: 'Title and content are required' },

{ status: 400 }

)

}

const response = await fetch('https://api.example.com/posts', {

method: 'POST',

headers: {

'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,

'Content-Type': 'application/json',

},

body: JSON.stringify(body),

})

const data = await response.json()

return NextResponse.json(data, { status: 201 })

} catch (error) {

return NextResponse.json(

{ error: 'Failed to create post' },

{ status: 500 }

)

}

}

```

Server Components:

```typescript

// app/posts/page.tsx

async function getPosts() {

const response = await fetch('https://api.example.com/posts', {

headers: {

'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,

},

next: { revalidate: 3600 }, // Revalidate every hour

})

if (!response.ok) {

throw new Error('Failed to fetch posts')

}

return response.json()

}

export default async function PostsPage() {

const posts = await getPosts()

return (

<div>

{posts.map((post: Post) => (

<PostCard key={post.id} post={post} />

))}

</div>

)

}

```

Server Actions:

```typescript

// app/actions/posts.ts

'use server'

import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {

const title = formData.get('title') as string

const content = formData.get('content') as string

const response = await fetch('https://api.example.com/posts', {

method: 'POST',

headers: {

'Authorization': `Bearer ${process.env.API_SECRET_KEY}`,

'Content-Type': 'application/json',

},

body: JSON.stringify({ title, content }),

})

if (!response.ok) {

throw new Error('Failed to create post')

}

revalidatePath('/posts')

return response.json()

}

```

Authentication

Handle API authentication securely:

API Keys:

```typescript

// Store in environment variables

const API_KEY = process.env.API_SECRET_KEY

// Include in requests

fetch('https://api.example.com/data', {

headers: {

'X-API-Key': API_KEY,

},

})

```

OAuth Tokens:

```typescript

// lib/api/auth.ts

export async function getAccessToken() {

const response = await fetch('https://oauth.example.com/token', {

method: 'POST',

headers: {

'Content-Type': 'application/x-www-form-urlencoded',

},

body: new URLSearchParams({

grant_type: 'client_credentials',

client_id: process.env.OAUTH_CLIENT_ID!,

client_secret: process.env.OAUTH_CLIENT_SECRET!,

}),

})

const data = await response.json()

return data.access_token

}

// Use token in requests

const token = await getAccessToken()

fetch('https://api.example.com/data', {

headers: {

'Authorization': `Bearer ${token}`,

},

})

```

JWT Tokens:

```typescript

import { SignJWT, jwtVerify } from 'jose'

export async function createToken(payload: any) {

const secret = new TextEncoder().encode(process.env.JWT_SECRET)

return new SignJWT(payload)

.setProtectedHeader({ alg: 'HS256' })

.setIssuedAt()

.setExpirationTime('2h')

.sign(secret)

}

export async function verifyToken(token: string) {

const secret = new TextEncoder().encode(process.env.JWT_SECRET)

try {

const { payload } = await jwtVerify(token, secret)

return payload

} catch (error) {

return null

}

}

```

Error Handling

Implement robust error handling:

Retry Logic:

```typescript

async function fetchWithRetry<T>(

url: string,

options?: RequestInit,

maxRetries = 3

): Promise<T> {

let lastError: Error

for (let i = 0; i < maxRetries; i++) {

try {

const response = await fetch(url, options)

if (!response.ok) {

throw new Error(`HTTP ${response.status}`)

}

return response.json()

} catch (error) {

lastError = error as Error

// Don't retry on client errors (4xx)

if (error instanceof Error && error.message.startsWith('HTTP 4')) {

throw error

}

// Wait before retrying (exponential backoff)

if (i < maxRetries - 1) {

await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000))

}

}

}

throw lastError!

}

```

Fallback UI:

```typescript

function DataDisplay() {

const { data, error, isLoading } = useSWR('/api/data', fetcher, {

fallbackData: cachedData, // Use cached data while revalidating

onError: (error) => {

// Log error to monitoring service

console.error('API Error:', error)

},

})

if (error) {

return (

<div className="text-center py-8">

<p className="text-destructive">Failed to load data</p>

<Button onClick={() => mutate()}>Retry</Button>

</div>

)

}

if (isLoading) return <Skeleton />

return <DataTable data={data} />

}

```

Rate Limiting

Respect API limits and implement client-side throttling:

Request Throttling:

```typescript

import { debounce } from 'es-toolkit'

// Debounce search requests

const debouncedSearch = debounce(async (query: string) => {

const results = await fetchAPI(`/search?q=${query}`)

setResults(results)

}, 300)

// Usage

<input onChange={(e) => debouncedSearch(e.target.value)} />

```

Request Queue:

```typescript

class RequestQueue {

private queue: Array<() => Promise<any>> = []

private processing = false

private maxConcurrent = 3

private active = 0

async add<T>(request: () => Promise<T>): Promise<T> {

return new Promise((resolve, reject) => {

this.queue.push(async () => {

try {

const result = await request()

resolve(result)

} catch (error) {

reject(error)

}

})

this.process()

})

}

private async process() {

if (this.processing || this.active >= this.maxConcurrent) return

const request = this.queue.shift()

if (!request) return

this.processing = true

this.active++

await request()

this.active--

this.processing = false

if (this.queue.length > 0) {

this.process()

}

}

}

const queue = new RequestQueue()

// Usage

queue.add(() => fetchAPI('/endpoint1'))

queue.add(() => fetchAPI('/endpoint2'))

```

Proper API integration ensures your application is reliable, performant, and maintainable.

Tags:
#api#integration#rest#graphql

Need Help with Your v0 Project?

Our team of v0 experts is ready to help you build amazing applications with cutting-edge AI technology.

Get in Touch