Serverless Platforms
Deploy serverless functions on Vercel, Cloudflare Workers, AWS Lambda, and more.
7 min read
Vercel Functions#
Best for Next.js and frontend projects:
Route Handlers (App Router)#
javascript
// app/api/users/route.js
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page')) || 1;
const users = await prisma.user.findMany({
take: 20,
skip: (page - 1) * 20,
});
return NextResponse.json({ data: users });
}
export async function POST(request) {
const body = await request.json();
const user = await prisma.user.create({
data: body,
});
return NextResponse.json({ data: user }, { status: 201 });
}
Dynamic Routes#
javascript
// app/api/users/[id]/route.js
import { NextResponse } from 'next/server';
export async function GET(request, { params }) {
const { id } = await params;
const user = await prisma.user.findUnique({
where: { id },
});
if (!user) {
return NextResponse.json(
{ error: 'User not found' },
{ status: 404 }
);
}
return NextResponse.json({ data: user });
}
export async function PATCH(request, { params }) {
const { id } = await params;
const body = await request.json();
const user = await prisma.user.update({
where: { id },
data: body,
});
return NextResponse.json({ data: user });
}
export async function DELETE(request, { params }) {
const { id } = await params;
await prisma.user.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
}
Edge Runtime#
javascript
// app/api/hello/route.js
export const runtime = 'edge'; // Use edge runtime
export async function GET(request) {
return new Response(JSON.stringify({
message: 'Hello from the edge!',
region: request.headers.get('x-vercel-ip-country'),
}), {
headers: { 'Content-Type': 'application/json' },
});
}
Middleware#
javascript
// middleware.js (root of project)
import { NextResponse } from 'next/server';
export function middleware(request) {
// Auth check
const token = request.cookies.get('token');
if (request.nextUrl.pathname.startsWith('/api/protected')) {
if (!token) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
}
// Add headers
const response = NextResponse.next();
response.headers.set('x-request-id', crypto.randomUUID());
return response;
}
export const config = {
matcher: '/api/:path*',
};
Cloudflare Workers#
Fastest cold starts, runs on edge globally:
Basic Worker#
javascript
// src/index.js
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
// Routing
if (url.pathname === '/api/hello') {
return handleHello(request, env);
}
if (url.pathname.startsWith('/api/users')) {
return handleUsers(request, env);
}
return new Response('Not Found', { status: 404 });
},
};
async function handleHello(request, env) {
return new Response(JSON.stringify({
message: 'Hello from Cloudflare!',
cf: request.cf, // Cloudflare request info
}), {
headers: { 'Content-Type': 'application/json' },
});
}
async function handleUsers(request, env) {
// Use D1 (Cloudflare's SQLite)
const users = await env.DB.prepare('SELECT * FROM users').all();
return new Response(JSON.stringify({ data: users.results }), {
headers: { 'Content-Type': 'application/json' },
});
}
With Hono (Recommended)#
bash
npm install hono
javascript
// src/index.js
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt';
const app = new Hono();
// Middleware
app.use('/*', cors());
app.use('/api/*', jwt({ secret: 'secret' }));
// Routes
app.get('/api/users', async (c) => {
const db = c.env.DB;
const users = await db.prepare('SELECT * FROM users').all();
return c.json({ data: users.results });
});
app.post('/api/users', async (c) => {
const body = await c.req.json();
const db = c.env.DB;
const result = await db.prepare(
'INSERT INTO users (email, name) VALUES (?, ?)'
).bind(body.email, body.name).run();
return c.json({ data: { id: result.lastRowId } }, 201);
});
app.get('/api/users/:id', async (c) => {
const id = c.req.param('id');
const db = c.env.DB;
const user = await db.prepare(
'SELECT * FROM users WHERE id = ?'
).bind(id).first();
if (!user) {
return c.json({ error: 'Not found' }, 404);
}
return c.json({ data: user });
});
export default app;
Cloudflare D1 (SQLite)#
bash
# Create database
wrangler d1 create my-database
# Run migrations
wrangler d1 execute my-database --file=./schema.sql
sql
-- schema.sql
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
name TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
toml
# wrangler.toml
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxx"
KV Storage#
javascript
// Key-Value storage
app.get('/api/cache/:key', async (c) => {
const key = c.req.param('key');
const value = await c.env.KV.get(key);
if (!value) {
return c.json({ error: 'Not found' }, 404);
}
return c.json({ data: JSON.parse(value) });
});
app.put('/api/cache/:key', async (c) => {
const key = c.req.param('key');
const body = await c.req.json();
await c.env.KV.put(key, JSON.stringify(body), {
expirationTtl: 3600, // 1 hour
});
return c.json({ success: true });
});
AWS Lambda#
Enterprise-grade serverless:
Basic Handler#
javascript
// handler.js
export const handler = async (event, context) => {
const body = JSON.parse(event.body || '{}');
// Process request
const result = await processData(body);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: result }),
};
};
With API Gateway#
javascript
// handler.js
export const getUsers = async (event) => {
const { page = '1' } = event.queryStringParameters || {};
const users = await prisma.user.findMany({
take: 20,
skip: (parseInt(page) - 1) * 20,
});
return {
statusCode: 200,
body: JSON.stringify({ data: users }),
};
};
export const getUser = async (event) => {
const { id } = event.pathParameters;
const user = await prisma.user.findUnique({
where: { id },
});
if (!user) {
return {
statusCode: 404,
body: JSON.stringify({ error: 'Not found' }),
};
}
return {
statusCode: 200,
body: JSON.stringify({ data: user }),
};
};
export const createUser = async (event) => {
const body = JSON.parse(event.body);
const user = await prisma.user.create({
data: body,
});
return {
statusCode: 201,
body: JSON.stringify({ data: user }),
};
};
Serverless Framework#
yaml
# serverless.yml
service: my-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
environment:
DATABASE_URL: ${env:DATABASE_URL}
functions:
getUsers:
handler: handler.getUsers
events:
- http:
path: users
method: get
cors: true
getUser:
handler: handler.getUser
events:
- http:
path: users/{id}
method: get
cors: true
createUser:
handler: handler.createUser
events:
- http:
path: users
method: post
cors: true
SST (Recommended)#
typescript
// sst.config.ts
export default {
config() {
return {
name: 'my-api',
region: 'us-east-1',
};
},
stacks(app) {
app.stack(function API({ stack }) {
const api = new Api(stack, 'api', {
routes: {
'GET /users': 'packages/functions/src/users.list',
'GET /users/{id}': 'packages/functions/src/users.get',
'POST /users': 'packages/functions/src/users.create',
},
});
stack.addOutputs({
ApiUrl: api.url,
});
});
},
};
Netlify Functions#
Simple setup for JAMstack:
javascript
// netlify/functions/users.js
import { prisma } from '../../lib/prisma';
export const handler = async (event, context) => {
if (event.httpMethod === 'GET') {
const users = await prisma.user.findMany();
return {
statusCode: 200,
body: JSON.stringify({ data: users }),
};
}
if (event.httpMethod === 'POST') {
const body = JSON.parse(event.body);
const user = await prisma.user.create({ data: body });
return {
statusCode: 201,
body: JSON.stringify({ data: user }),
};
}
return {
statusCode: 405,
body: JSON.stringify({ error: 'Method not allowed' }),
};
};
Platform Comparison#
| Feature | Vercel | Cloudflare | AWS Lambda | Netlify |
|---|---|---|---|---|
| Cold start | ~100ms | ~0ms | ~200-500ms | ~200ms |
| Max duration | 60s (hobby) | 30s (free) | 15min | 26s |
| Edge support | Yes | Native | Via Lambda@Edge | Yes |
| Database | External | D1, Turso | RDS, DynamoDB | External |
| WebSockets | No | Yes (Durable Objects) | Via API Gateway | No |
| Pricing | Generous free | Generous free | Pay per use | Generous free |
Key Takeaways#
- Vercel - Best for Next.js projects
- Cloudflare Workers - Best for edge, low latency
- AWS Lambda - Best for AWS ecosystem
- Use frameworks - Hono, SST make development easier
- Consider cold starts - Edge functions have near-zero latency
Continue Learning
Ready to level up your skills?
Explore more guides and tutorials to deepen your understanding and become a better developer.