Skip to content

Serverless Functions

Frontend AI runs using Next.js, and files marked with route.tsx will execute serverless functions. Functions allow you to securely execute code that should not be visible in the browser, keeping sensitive logic and API keys protected.

Your functions have access to:

  • process.env: Environment variables from your project
  • params: URL parameters from dynamic routes
  • query: Query string parameters from the request URL
  • body: Request body data (for POST, PUT, PATCH requests)

Use native fetch to retrieve data from external APIs.

app/api/weather/route.tsx
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const city = searchParams.get('city');
const response = await fetch(
`https://api.weather.com/v1/current?city=${city}`,
{
headers: { 'Authorization': `Bearer ${process.env.WEATHER_API_KEY}` }
}
);
const data = await response.json();
return NextResponse.json(data);
}

Connect to a Neon Postgres database to run SQL queries.

app/api/users/route.tsx
import { NextRequest, NextResponse } from 'next/server';
import { neon } from '@neondatabase/serverless';
export async function GET(request: NextRequest) {
const sql = neon(process.env.DATABASE_URL!);
const users = await sql`SELECT id, name, email FROM users LIMIT 10`;
return NextResponse.json({ users });
}
export async function POST(request: NextRequest) {
const sql = neon(process.env.DATABASE_URL!);
const { name, email } = await request.json();
const result = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING id, name, email
`;
return NextResponse.json({ user: result[0] });
}

Create streaming responses for real-time AI interactions.

app/api/chat/route.tsx
import { NextRequest } from 'next/server';
import OpenAI from 'openai';
export async function POST(request: NextRequest) {
const { messages } = await request.json();
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
const stream = await openai.chat.completions.create({
model: 'gpt-5.2',
messages,
stream: true
});
const encoder = new TextEncoder();
const readable = new ReadableStream({
async start(controller) {
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content || '';
controller.enqueue(encoder.encode(text));
}
controller.close();
}
});
return new Response(readable, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' }
});
}

Use Drizzle ORM with Neon for more advanced database operations.

app/api/query/route.tsx
import { NextResponse } from 'next/server';
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import { sql } from 'drizzle-orm';
export async function POST(request: Request) {
try {
const { query } = await request.json();
if (!query) {
return NextResponse.json(
{ error: 'SQL query is required' },
{ status: 400 }
);
}
const client = neon(process.env.DATABASE_URL!);
const db = drizzle(client);
// Execute raw SQL using drizzle's sql template
const result = await db.execute(sql.raw(query));
return NextResponse.json({ data: result });
} catch (error: any) {
console.error('Database query error:', error);
return NextResponse.json(
{ error: error.message || 'Failed to execute query' },
{ status: 500 }
);
}
}

Verify user authentication on the server using Supabase.

app/api/auth/verify/route.tsx
import { NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
export async function POST(request: Request) {
try {
const { access_token } = await request.json();
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
// Create a Supabase client with the user's access token
const supabase = createClient(supabaseUrl, supabaseAnonKey, {
global: {
headers: {
Authorization: `Bearer ${access_token}`,
}
}
});
// Get the user using the access token
const { data: { user }, error } = await supabase.auth.getUser(access_token);
if (error || !user) {
return NextResponse.json(
{ error: 'Invalid or expired token' },
{ status: 401 }
);
}
return NextResponse.json({ user });
} catch (error: any) {
return NextResponse.json(
{ error: error.message || 'Authentication failed' },
{ status: 500 }
);
}
}

Each time you invoke a function, query logs are available in the Code > Functions tab. The logs help you debug and monitor your functions by showing:

  • Status code: The HTTP response status (200, 400, 500, etc.)
  • Payload: The request data sent to the function
  • Console logs: Any console.log output from your function
  • Response: The data returned from the server

Use these logs to verify that your functions are executing correctly and to troubleshoot any errors that occur.