Your First API
Learn how to create a fully type-safe API endpoint in three steps: define a route, register it, and use it in your Next.js app.
Step 1: Define the Route
Routes are defined using the route helper with TypeBox schemas for validation.
typescript// src/server/routes/users.ts import { route } from '@spfn/core/route'; import { Type } from '@sinclair/typebox'; import { NotFoundError } from '@spfn/core/errors'; import { UserRepository } from '../repositories/user.repository'; const userRepo = new UserRepository(); /** * POST /users - Create a new user */ export const createUser = route.post('/users') .input({ body: Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }) }) }) .handler(async (c) => { const { body } = await c.data(); const user = await userRepo.create({ name: body.name, email: body.email }); return c.created(user); }); /** * GET /users/:id - Get user by ID */ export const getUser = route.get('/users/:id') .input({ params: Type.Object({ id: Type.String() }) }) .handler(async (c) => { const { params } = await c.data(); const user = await userRepo.findById(params.id); if (!user) { throw new NotFoundError({ resource: 'User' }); } return user; });
Step 2: Register Routes
Add your routes to the application router for type inference and client generation.
typescript// src/server/router.ts import { defineRouter } from '@spfn/core/route'; import { createUser, getUser } from './routes/users'; export const appRouter = defineRouter({ createUser, getUser, // ... other routes }); export type AppRouter = typeof appRouter;
Step 3: Generate Client & Use in Next.js
Generate a type-safe client and use it anywhere in your Next.js app.
Terminal# Generate type-safe client pnpm spfn codegen router
typescript// app/page.tsx 'use client'; import { api } from '@/lib/api-client'; import { NotFoundError } from '@spfn/core/errors'; export default function Home() { const handleCreateUser = async () => { // Create user - fully typed! const user = await api.createUser .body({ name: 'John Doe', email: 'john@example.com' }) .call(); console.log(user.id); // ✅ Typed! }; const handleGetUser = async (id: string) => { try { const user = await api.getUser .params({ id }) .call(); console.log(user.name); // ✅ Typed! } catch (error) { if (error instanceof NotFoundError) { console.log('User not found:', error.resource); } } }; return ( <button onClick={handleCreateUser}> Create User </button> ); }
That's it!
You've created your first type-safe API with Superfunction. The types flow automatically from route definition to client usage.
What Happens Behind the Scenes?
- Superfunction validates request body against the TypeBox schema
- TypeScript ensures your handler returns the correct response type
- The CLI generates a type-safe client with full autocomplete
- All types are inferred—no manual type definitions needed!
Route Builder API
The route builder provides a chainable API:
typescriptroute.get('/path/:id') // HTTP method + path .input({ // Input validation params: Type.Object({ id: Type.String() }), query: Type.Object({ limit: Type.Number() }), body: Type.Object({ name: Type.String() }), headers: Type.Object({ authorization: Type.String() }), }) .middleware([auth]) // Route-specific middleware .skip(['rateLimit']) // Skip global middleware .handler(async (c) => { // Handler function const { params, query, body, headers } = await c.data(); return { ... }; });
Response Helpers
The context provides convenient response helpers:
typescript.handler(async (c) => { // Return data directly (200 OK) return { id: '123', name: 'John' }; // Or use response helpers: return c.created(user); // 201 Created return c.accepted({ jobId }); // 202 Accepted return c.noContent(); // 204 No Content return c.paginated(items, page, limit, total); // Paginated response });
Next Steps
Core Concepts
Dive deeper into routes, middleware, and type safety.
Guides
Learn how to handle authentication, transactions, and more.