Serverless architecture eliminates the need to provision, scale, and maintain servers. You write functions, deploy them, and the cloud provider handles everything else — scaling to zero when idle and to thousands of instances under load. This guide covers practical setup across AWS, Google Cloud, Vercel, and Azure with real-world code you can deploy today.
What Serverless Actually Means
The name is misleading — there are still servers, you just do not manage them. In a traditional model, you rent a VPS (Virtual Private Server), install an OS, configure Nginx, deploy your app, and monitor uptime around the clock. If traffic spikes, you scramble to add more instances. With serverless, you write a function, push it to the cloud, and the provider runs it on demand. You pay only for the milliseconds your code executes.
The key characteristics of serverless:
- No server management: Zero patching, zero capacity planning, zero SSH sessions.
- Pay-per-execution: Billed per request and compute time (often in 1ms increments).
- Auto-scaling: Scales from 0 to 10,000 concurrent executions without configuration.
- Event-driven: Functions are triggered by events — HTTP requests, database changes, file uploads, schedules.
AWS Lambda: Full Setup with SAM CLI
AWS Lambda is the most mature serverless platform with the largest ecosystem. The AWS Serverless Application Model (SAM) CLI simplifies development, testing, and deployment.
Installation
# Install AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# Configure AWS credentials
aws configure
# Enter: Access Key ID, Secret Access Key, Region (us-east-1), Output (json)
# Install SAM CLI
pip install aws-sam-cli
# Verify installation
sam --version
# SAM CLI, version 1.120.0
Create a Lambda Project
# Initialize a new SAM project
sam init --runtime nodejs20.x --name my-api --app-template hello-world
# Project structure
my-api/
├── README.md
├── events/
│ └── event.json
├── hello-world/
│ ├── app.mjs
│ ├── package.json
│ └── tests/
└── template.yaml
SAM Template (template.yaml)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: DreamWebCrafts API
Globals:
Function:
Timeout: 10
Runtime: nodejs20.x
MemorySize: 256
Environment:
Variables:
NODE_ENV: production
DB_CONNECTION: !Ref DatabaseUrl
Parameters:
DatabaseUrl:
Type: String
Description: Database connection string
NoEcho: true
Resources:
# API Gateway
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization'"
AllowOrigin: "'https://dreamwebcrafts.com'"
# GET /users
GetUsersFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: functions/users/
Handler: getUsers.handler
Events:
GetUsers:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: get
# POST /users
CreateUserFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: functions/users/
Handler: createUser.handler
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref UsersTable
Events:
CreateUser:
Type: Api
Properties:
RestApiId: !Ref ApiGateway
Path: /users
Method: post
# DynamoDB Table
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: dreamweb-users
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Outputs:
ApiUrl:
Description: API Gateway URL
Value: !Sub "https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/prod/"
Lambda Function Code
// functions/users/getUsers.mjs
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event) => {
try {
const command = new ScanCommand({
TableName: 'dreamweb-users',
Limit: 50
});
const result = await docClient.send(command);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'https://dreamwebcrafts.com'
},
body: JSON.stringify({
users: result.Items,
count: result.Count
})
};
} catch (error) {
console.error('Error fetching users:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Internal server error' })
};
}
};
Local Testing and Deployment
# Test locally with SAM
sam local invoke GetUsersFunction --event events/event.json
# Start local API Gateway
sam local start-api --port 3000
# Test the endpoint
curl http://localhost:3000/users
# Deploy to AWS
sam build
sam deploy --guided
# Follow the prompts: stack name, region, confirm changes
Google Cloud Functions
# Install Google Cloud CLI
curl https://sdk.cloud.google.com | bash
gcloud init
# Create a Cloud Function
mkdir my-function && cd my-function
// index.js - Google Cloud Function
const functions = require('@google-cloud/functions-framework');
functions.http('helloWorld', (req, res) => {
const name = req.query.name || req.body.name || 'World';
// CORS headers
res.set('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') {
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.status(204).send('');
return;
}
res.json({
message: `Hello ${name}!`,
timestamp: new Date().toISOString(),
region: process.env.FUNCTION_REGION
});
});
# Deploy to Google Cloud
gcloud functions deploy helloWorld
--runtime nodejs20
--trigger-http
--allow-unauthenticated
--region us-central1
--memory 256MB
--timeout 30s
Vercel Edge Functions
Vercel's Edge Functions run on Cloudflare's network at 300+ edge locations worldwide, giving you sub-50ms response times globally. They are ideal for Next.js middleware, A/B testing, and geo-routing.
// app/api/hello/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from 'next/server';
export const runtime = 'edge'; // Run on the edge network
export async function GET(request: NextRequest) {
const country = request.geo?.country || 'Unknown';
const city = request.geo?.city || 'Unknown';
return NextResponse.json({
message: 'Hello from the Edge!',
location: { country, city },
timestamp: Date.now()
});
}
export async function POST(request: NextRequest) {
const body = await request.json();
// Validate input
if (!body.email) {
return NextResponse.json(
{ error: 'Email is required' },
{ status: 400 }
);
}
// Process the request
return NextResponse.json({
success: true,
message: 'Subscription created'
});
}
# Deploy to Vercel
npm i -g vercel
vercel --prod
Azure Functions
# Install Azure Functions Core Tools
npm install -g azure-functions-core-tools@4
# Create a new project
func init MyFunctionApp --javascript
cd MyFunctionApp
func new --name HttpTrigger --template "HTTP trigger"
# Run locally
func start
# Deploy to Azure
az login
func azure functionapp publish MyFunctionApp
Serverless Database Options
Serverless functions need serverless databases. Traditional connection-based databases like MySQL or PostgreSQL choke under serverless workloads because each function invocation opens a new connection, exhausting the connection pool in seconds.
| Database | Type | Free Tier | Best For | Connection Model |
|---|---|---|---|---|
| PlanetScale | MySQL-compatible | 1 database, 5GB | Production MySQL workloads | HTTP (serverless driver) |
| Neon | PostgreSQL | 0.5GB, auto-suspend | PostgreSQL with branching | HTTP + WebSocket |
| Supabase | PostgreSQL + Auth + Storage | 500MB, 2 projects | Full backend replacement | REST API + Realtime |
| DynamoDB | NoSQL (key-value) | 25GB, 25 read/write units | AWS-native applications | AWS SDK (HTTP) |
| Turso | SQLite (distributed) | 9GB, 500 databases | Edge computing, embedded | HTTP + libSQL |
| Upstash Redis | Key-value / Cache | 10,000 commands/day | Caching, rate limiting, queues | REST API |
Example: Neon Serverless PostgreSQL
// Using Neon's serverless driver (no connection pooling needed)
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL);
export async function handler(event) {
const users = await sql`SELECT id, name, email FROM users LIMIT 20`;
return {
statusCode: 200,
body: JSON.stringify(users)
};
}
API Gateway Configuration
An API Gateway sits in front of your Lambda functions, handling routing, authentication, rate limiting, and CORS. Here is a complete AWS API Gateway configuration with custom domain:
# serverless.yml (Serverless Framework)
service: dreamweb-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stage: ${opt:stage, 'dev'}
environment:
DATABASE_URL: ${ssm:/dreamweb/${self:provider.stage}/database-url}
JWT_SECRET: ${ssm:/dreamweb/${self:provider.stage}/jwt-secret~true}
functions:
getUsers:
handler: src/handlers/users.getAll
events:
- httpApi:
path: /api/users
method: GET
memorySize: 256
timeout: 10
createUser:
handler: src/handlers/users.create
events:
- httpApi:
path: /api/users
method: POST
processImage:
handler: src/handlers/images.process
events:
- s3:
bucket: dreamweb-uploads
event: s3:ObjectCreated:*
rules:
- suffix: .jpg
- suffix: .png
memorySize: 1024
timeout: 30
dailyReport:
handler: src/handlers/reports.daily
events:
- schedule: cron(0 8 * * ? *) # 8 AM UTC daily
custom:
customDomain:
domainName: api.dreamwebcrafts.com
certificateName: '*.dreamwebcrafts.com'
basePath: ''
stage: ${self:provider.stage}
plugins:
- serverless-domain-manager
- serverless-offline
Cold Start Optimization
Cold starts are the Achilles' heel of serverless. When a function has not been invoked recently, the cloud provider must spin up a new execution environment, which adds 100ms to 2 seconds of latency. Here is how to minimize this:
Strategies to Reduce Cold Starts
- Keep dependencies minimal: A 50MB deployment package takes longer to initialize than a 5MB one. Audit your
node_modulesruthlessly. - Use provisioned concurrency (AWS): Pre-warm N instances that are always ready.
- Initialize outside the handler: Database connections and SDK clients created outside the handler function persist across invocations.
- Choose lightweight runtimes: Python and Node.js cold start in ~200ms. Java and .NET cold start in 1-3 seconds.
- Use arm64 architecture: AWS Graviton2 processors offer 20% better price-performance and faster cold starts.
// WRONG: Client initialized inside handler (cold start every time)
export const handler = async (event) => {
const client = new DynamoDBClient({}); // Created on EVERY invocation
// ...
};
// RIGHT: Client initialized outside handler (reused across invocations)
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event) => {
// client is reused from the previous invocation's warm container
// ...
};
Environment Variables and Secrets Management
# AWS SSM Parameter Store (encrypted secrets)
aws ssm put-parameter
--name "/dreamweb/prod/database-url"
--value "postgresql://user:pass@host/db"
--type SecureString
# Retrieve in Lambda (via SAM template)
# Environment:
# Variables:
# DATABASE_URL: !Sub '{{resolve:ssm:/dreamweb/prod/database-url}}'
# Vercel environment variables
vercel env add DATABASE_URL production
vercel env add JWT_SECRET production
# Google Cloud Secret Manager
echo -n "my-secret-value" | gcloud secrets create db-password --data-file=-
gcloud secrets versions access latest --secret=db-password
Cost Calculation Examples
Understanding serverless costs is essential for budgeting. Here are real-world calculations for AWS Lambda:
| Scenario | Requests/Month | Duration | Memory | Monthly Cost |
|---|---|---|---|---|
| Personal blog API | 100,000 | 50ms avg | 128MB | ~$0.20 |
| Startup SaaS | 5,000,000 | 200ms avg | 256MB | ~$18.00 |
| E-commerce platform | 50,000,000 | 150ms avg | 512MB | ~$250.00 |
| Same on a t3.medium EC2 | Unlimited | N/A | 4GB | ~$30.00 + ops time |
The crossover point: At roughly 10-20 million requests per month with high compute times, a dedicated server becomes cheaper than Lambda. Below that threshold, serverless wins on both cost and operational overhead.
Monitoring with CloudWatch
# View Lambda logs
aws logs tail /aws/lambda/my-function --follow
# Create a CloudWatch alarm for errors
aws cloudwatch put-metric-alarm
--alarm-name "LambdaErrors"
--metric-name Errors
--namespace AWS/Lambda
--statistic Sum
--period 300
--threshold 5
--comparison-operator GreaterThanThreshold
--dimensions Name=FunctionName,Value=my-function
--evaluation-periods 1
--alarm-actions arn:aws:sns:us-east-1:123456789:alerts
When NOT to Use Serverless
Serverless is powerful but not universal. Avoid it when:
- Long-running processes: Lambda has a 15-minute maximum execution time. Batch processing, video encoding, and ML training do not fit.
- WebSocket connections: Persistent connections are expensive in serverless. Use dedicated servers or managed services like AWS AppSync.
- High-throughput, steady workloads: If your server runs at 70-80% capacity 24/7, a reserved EC2 instance is cheaper than millions of Lambda invocations.
- Complex orchestration: When one request needs to call 5 internal services sequentially, the accumulated cold starts and network latency degrade the user experience.
- Stateful applications: Serverless functions are stateless by design. If your application requires in-memory state (game servers, real-time collaboration), use containers instead.
Troubleshooting Serverless Issues
Problem: "Task timed out after 10.00 seconds"
Cause: Your function exceeded the configured timeout, usually because of a slow database query, external API call, or unresolved Promise.
Solution: Increase the timeout in your template. Add connection timeouts to external calls. Check for unresolved Promises in async code. Use context.getRemainingTimeInMillis() to implement graceful shutdown.
Problem: "ECONNREFUSED" when connecting to RDS
Cause: Lambda runs in a VPC that cannot reach your RDS instance, or the security group does not allow inbound connections from the Lambda function.
Solution: Place your Lambda in the same VPC as your RDS. Add the Lambda security group to the RDS inbound rules. Use RDS Proxy to handle connection pooling.
Problem: Deployment package is too large (>250MB)
Cause: Your node_modules directory includes unnecessary dependencies.
Solution: Use esbuild or webpack to bundle your code. Move large dependencies to Lambda Layers. Use --production flag when installing: npm install --production.
Quick Reference: Serverless Cheat Sheet
| Task | AWS Command |
|---|---|
| Create SAM project | sam init --runtime nodejs20.x |
| Test locally | sam local start-api |
| Deploy | sam build && sam deploy --guided |
| View logs | sam logs -n FunctionName --tail |
| Invoke function | aws lambda invoke --function-name my-func output.json |
| Set secret | aws ssm put-parameter --name /app/key --value "val" --type SecureString |
| Delete stack | sam delete --stack-name my-stack |
| Check cold starts | aws cloudwatch get-metric-statistics --metric-name Duration |
Serverless is not a trend — it is the default for modern API development, event processing, and microservices. At DreamWebCrafts, we architect serverless backends for clients who need scalability without the operational burden of managing servers. Whether you are building a startup MVP or migrating an enterprise workload, our team can design a serverless architecture that fits your budget and performance requirements. Reach out to discuss your project.