# Serverless Architecture: Building Modern Applications
Master AWS Lambda, Vercel, Cloudflare Workers and serverless best practices
Serverless lets developers focus on business logic. This article explores core concepts and practices.
Serverless Overview
What is Serverless
Serverless Core Features:
┌─────────────────────────────────────────────────────┐
│ │
│ No Server Management │
│ └── Infrastructure managed by cloud provider │
│ │
│ Pay Per Use │
│ └── Only pay for actual resource usage │
│ │
│ Auto Scaling │
│ └── Automatically adjust resources by load │
│ │
│ Event Driven │
│ └── Execute in response to events │
│ │
└─────────────────────────────────────────────────────┘
| Comparison | Traditional | Serverless |
|---|---|---|
| Operations | Required | Not needed |
| Scaling | Manual | Automatic |
| Billing | By time | By invocation |
| Cold Start | None | Exists |
AWS Lambda
Basic Function
// handler.ts
import { APIGatewayProxyHandler } from 'aws-lambda';
export const hello: APIGatewayProxyHandler = async (event) => {
const name = event.queryStringParameters?.name || 'World';
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
}),
};
};
// Handle POST requests
export const createUser: APIGatewayProxyHandler = async (event) => {
try {
const body = JSON.parse(event.body || '{}');
// Validate input
if (!body.email || !body.name) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Missing required fields' }),
};
}
// Create user
const user = await db.user.create({ data: body });
return {
statusCode: 201,
body: JSON.stringify(user),
};
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' }),
};
}
};
Serverless Framework Config
# serverless.yml
service: my-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
memorySize: 256
timeout: 10
environment:
DATABASE_URL: ${ssm:/my-app/database-url}
JWT_SECRET: ${ssm:/my-app/jwt-secret}
functions:
hello:
handler: handler.hello
events:
- http:
path: /hello
method: get
cors: true
createUser:
handler: handler.createUser
events:
- http:
path: /users
method: post
cors: true
processQueue:
handler: handler.processQueue
events:
- sqs:
arn: !GetAtt MyQueue.Arn
batchSize: 10
resources:
Resources:
MyQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: my-queue
Vercel Functions
Edge Functions
// app/api/hello/route.ts (Next.js App Router)
import { NextResponse } from 'next/server';
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const name = searchParams.get('name') || 'World';
return NextResponse.json({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
});
}
export async function POST(request: Request) {
const body = await request.json();
// Process request
const result = await processData(body);
return NextResponse.json(result, { status: 201 });
}
API Routes
// pages/api/users.ts (Pages Router)
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'GET':
const users = await db.user.findMany();
return res.json(users);
case 'POST':
const user = await db.user.create({ data: req.body });
return res.status(201).json(user);
default:
return res.status(405).json({ error: 'Method not allowed' });
}
}
// Configuration
export const config = {
api: {
bodyParser: {
sizeLimit: '1mb',
},
},
};
Cloudflare Workers
Basic Worker
// src/index.ts
export interface Env {
DATABASE: D1Database;
KV: KVNamespace;
BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// Routing
if (url.pathname === '/api/hello') {
return handleHello(request);
}
if (url.pathname.startsWith('/api/users')) {
return handleUsers(request, env);
}
return new Response('Not Found', { status: 404 });
},
};
async function handleHello(request: Request): Promise<Response> {
return new Response(
JSON.stringify({ message: 'Hello from Cloudflare Workers!' }),
{
headers: { 'Content-Type': 'application/json' },
}
);
}
async function handleUsers(request: Request, env: Env): Promise<Response> {
if (request.method === 'GET') {
const users = await env.DATABASE.prepare('SELECT * FROM users').all();
return Response.json(users.results);
}
if (request.method === 'POST') {
const body = await request.json();
await env.DATABASE.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(body.name, body.email)
.run();
return Response.json({ success: true }, { status: 201 });
}
return new Response('Method not allowed', { status: 405 });
}
Hono Framework
// src/index.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { jwt } from 'hono/jwt';
type Bindings = {
DATABASE: D1Database;
JWT_SECRET: string;
};
const app = new Hono<{ Bindings: Bindings }>();
// Middleware
app.use('*', cors());
app.use('/api/*', jwt({ secret: (c) => c.env.JWT_SECRET }));
// Routes
app.get('/api/users', async (c) => {
const db = c.env.DATABASE;
const users = await db.prepare('SELECT * FROM users').all();
return c.json(users.results);
});
app.post('/api/users', async (c) => {
const db = c.env.DATABASE;
const body = await c.req.json();
await db.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(body.name, body.email)
.run();
return c.json({ success: true }, 201);
});
app.get('/api/users/:id', async (c) => {
const id = c.req.param('id');
const db = c.env.DATABASE;
const user = await db.prepare('SELECT * FROM users WHERE id = ?')
.bind(id)
.first();
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json(user);
});
export default app;
Database Connections
Serverless Databases
// Prisma with Accelerate
import { PrismaClient } from '@prisma/client/edge';
import { withAccelerate } from '@prisma/extension-accelerate';
const prisma = new PrismaClient().$extends(withAccelerate());
export async function getUsers() {
return prisma.user.findMany({
cacheStrategy: { ttl: 60 }, // Cache for 60 seconds
});
}
// Neon Serverless
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
export async function getUsers() {
return sql`SELECT * FROM users`;
}
// PlanetScale
import { connect } from '@planetscale/database';
const conn = connect({
url: process.env.DATABASE_URL,
});
export async function getUsers() {
const results = await conn.execute('SELECT * FROM users');
return results.rows;
}
Cold Start Optimization
// 1. Reduce dependencies
// Use lightweight libraries
import { z } from 'zod'; // Instead of joi
import dayjs from 'dayjs'; // Instead of moment
// 2. Connection reuse
let dbConnection: Connection | null = null;
async function getConnection() {
if (!dbConnection) {
dbConnection = await createConnection();
}
return dbConnection;
}
// 3. Warmup function
export async function warmup() {
// Preload resources
await getConnection();
return { statusCode: 200, body: 'Warmed up' };
}
// 4. Use Provisioned Concurrency (AWS)
// serverless.yml
functions:
api:
handler: handler.api
provisionedConcurrency: 5
Event-Driven Patterns
// Handle S3 events
export const processUpload = async (event: S3Event) => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
// Process uploaded file
const file = await s3.getObject({ Bucket: bucket, Key: key });
await processFile(file);
}
};
// Handle SQS messages
export const processMessage = async (event: SQSEvent) => {
for (const record of event.Records) {
const message = JSON.parse(record.body);
await handleMessage(message);
}
};
// Scheduled tasks
export const scheduledTask = async (event: ScheduledEvent) => {
console.log('Running scheduled task:', event.time);
await cleanupOldRecords();
await sendDailyReport();
};
Best Practices Summary
Serverless Best Practices:
┌─────────────────────────────────────────────────────┐
│ Performance │
│ ├── Reduce dependency size │
│ ├── Reuse database connections │
│ ├── Use Edge Functions │
│ └── Configure warmup strategies │
│ │
│ Architecture │
│ ├── Single responsibility functions │
│ ├── Stateless design │
│ ├── Event-driven decoupling │
│ └── Idempotent processing │
│ │
│ Security │
│ ├── Least privilege principle │
│ ├── Encrypt environment variables │
│ ├── Input validation │
│ └── Rate limiting │
│ │
│ Observability │
│ ├── Structured logging │
│ ├── Distributed tracing │
│ ├── Metrics monitoring │
│ └── Alert configuration │
└─────────────────────────────────────────────────────┘
| Platform | Use Case |
|---|---|
| AWS Lambda | Enterprise, complex integrations |
| Vercel Functions | Next.js applications |
| Cloudflare Workers | Edge computing, low latency |
| Supabase Functions | With Supabase ecosystem |
Serverless isn’t a silver bullet, but it lets developers focus on creating value.