How to use serverless framework in Node.js
Serverless Framework simplifies deploying and managing serverless functions across multiple cloud providers with declarative configuration and automated infrastructure management. As the creator of CoreUI, a widely used open-source UI library, I’ve deployed serverless Node.js applications for scalable backend services throughout my 12 years of development experience since 2014. The most efficient approach is defining functions and resources in serverless.yml configuration file and using Serverless CLI for deployment automation. This method provides infrastructure as code, multi-cloud support, and local development tools for testing functions before deployment.
Install Serverless Framework globally and create a new serverless service with AWS Lambda.
npm install -g serverless
serverless create --template aws-nodejs --path my-service
cd my-service
npm init -y
npm install
Configure serverless.yml for AWS Lambda functions:
# serverless.yml
service: my-serverless-api
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stage: ${opt:stage, 'dev'}
memorySize: 256
timeout: 30
environment:
STAGE: ${self:provider.stage}
TABLE_NAME: ${self:custom.tableName}
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- Fn::GetAtt:
- UsersTable
- Arn
custom:
tableName: users-table-${self:provider.stage}
functions:
hello:
handler: handler.hello
events:
- http:
path: hello
method: get
cors: true
createUser:
handler: handlers/users.create
events:
- http:
path: users
method: post
cors: true
getUser:
handler: handlers/users.get
events:
- http:
path: users/{id}
method: get
cors: true
listUsers:
handler: handlers/users.list
events:
- http:
path: users
method: get
cors: true
updateUser:
handler: handlers/users.update
events:
- http:
path: users/{id}
method: put
cors: true
deleteUser:
handler: handlers/users.delete
events:
- http:
path: users/{id}
method: delete
cors: true
scheduledJob:
handler: handlers/scheduled.cleanup
events:
- schedule:
rate: cron(0 2 * * ? *)
enabled: true
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: ${self:custom.tableName}
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
plugins:
- serverless-offline
Create handler functions:
// handler.js
export const hello = async (event) => {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: 'Hello from Serverless!',
timestamp: new Date().toISOString()
})
}
}
// handlers/users.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { DynamoDBDocumentClient, PutCommand, GetCommand, ScanCommand, UpdateCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb'
const client = new DynamoDBClient({})
const docClient = DynamoDBDocumentClient.from(client)
const tableName = process.env.TABLE_NAME
export const create = async (event) => {
try {
const data = JSON.parse(event.body)
const id = Date.now().toString()
const item = {
id,
name: data.name,
email: data.email,
createdAt: new Date().toISOString()
}
await docClient.send(new PutCommand({
TableName: tableName,
Item: item
}))
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(item)
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
}
}
}
export const get = async (event) => {
try {
const { id } = event.pathParameters
const result = await docClient.send(new GetCommand({
TableName: tableName,
Key: { id }
}))
if (!result.Item) {
return {
statusCode: 404,
body: JSON.stringify({ error: 'User not found' })
}
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(result.Item)
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
}
}
}
export const list = async (event) => {
try {
const result = await docClient.send(new ScanCommand({
TableName: tableName
}))
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
users: result.Items,
count: result.Count
})
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
}
}
}
export const update = async (event) => {
try {
const { id } = event.pathParameters
const data = JSON.parse(event.body)
const result = await docClient.send(new UpdateCommand({
TableName: tableName,
Key: { id },
UpdateExpression: 'set #name = :name, email = :email, updatedAt = :updatedAt',
ExpressionAttributeNames: {
'#name': 'name'
},
ExpressionAttributeValues: {
':name': data.name,
':email': data.email,
':updatedAt': new Date().toISOString()
},
ReturnValues: 'ALL_NEW'
}))
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(result.Attributes)
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
}
}
}
export const deleteUser = async (event) => {
try {
const { id } = event.pathParameters
await docClient.send(new DeleteCommand({
TableName: tableName,
Key: { id }
}))
return {
statusCode: 204,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
} catch (error) {
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
}
}
}
Install and use serverless-offline for local development:
npm install --save-dev serverless-offline
serverless offline start
Deploy to AWS:
# Deploy to dev stage
serverless deploy
# Deploy to production stage
serverless deploy --stage production
# Deploy single function
serverless deploy function --function createUser
# View logs
serverless logs --function createUser --tail
# Remove service
serverless remove
Here the serverless.yml defines service configuration with provider settings, function definitions, and AWS resources declaratively. The functions section maps handler functions to HTTP endpoints with automatic API Gateway configuration. The events array specifies triggers including HTTP requests, scheduled cron jobs, and AWS service events. The resources section defines CloudFormation resources like DynamoDB tables deployed with the service. The handler functions return objects with statusCode, headers, and body following AWS Lambda proxy integration format. The serverless-offline plugin enables local testing without deploying to AWS for faster development cycles. The deploy command packages functions, creates CloudFormation stack, and deploys all infrastructure automatically.
Best Practice Note:
This is the serverless deployment approach we use for CoreUI backend services requiring auto-scaling and pay-per-use pricing models. Use environment variables for stage-specific configuration and secrets management with AWS Systems Manager Parameter Store, implement proper error handling and logging using AWS CloudWatch for production monitoring, and consider implementing API Gateway request validation and throttling to protect functions from malicious traffic and unexpected cost spikes.



