How do I structure a monorepo serverless project with the Serverless Framework?

Serverless Guru
Mar 3 · 5 min read
Credit: Pixabay

In this article, we are going to talk about how to structure a monorepo serverless project with the Serverless Framework.

Ideal — Project Structure

Below you will see a high level view of what a serverless monorepo project may look like when working with the Serverless Framework.

lib/  # <-- shared service code
response.js # <-- adds CORS headers, success/failure methods
resources/ # <-- shared AWS resources
api/ # <-- creates shell api, attaches auth
db/ # <-- creates an aurora serverless database
vpc/ # <-- creates an AWS VPC for private/public subnets
services/ # <-- logical groupings of functionality
billing/ # <-- arbitrary service
src/ # <-- organizes business logic for cloud function
handlers/ # <-- lambda handlers
invoices/
index.js
collections/
index.js
serverless.yml # <-- reuses shell api

Let’s break down the top-level directories:

  • lib — shared service code
  • resources — shared AWS resources
  • services— logical groupings of functionality

Lib — shared service code

Inside of our lib directory we are going to have some shared code which each individual service will have the ability to pull in.

lib/  # <-- shared service code
response.js # <-- adds CORS headers, success/failure methods

In the example above we have a file called response.js . This file will handle the response methods for our AWS Lambda functions.

// lib/response.jsconst response = {};let response = {};response.success = ({ body }) => {
return {
body: JSON.stringify(body),
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "*"
}
}
};
response.failure = ({ error }) => {
return {
body: JSON.stringify(error),
statusCode: 500,
headers: {
"Access-Control-Allow-Origin": "*"
}
}
};
module.exports = response;

Above you can see above we are simply adding a body , statusCode , and headers to our AWS Lambda response. This will now lower future work for all developers writing AWS Lambda functions and prevent issues with CORS, which is one of the most common problems developers will initially hit when developing serverless REST APIs.

resources — shared AWS resources

Inside of our resources/ directory we are going to have additional serverless stacks which will be reused across all of our services.

resources/  # <-- shared AWS resources
api/ # <-- creates shell api, attaches auth
db/ # <-- creates an aurora serverless database
vpc/ # <-- creates an AWS VPC for private/public subnets

If we expanded these subfolders we would see a structure like this.

resources/
api/
serverless.yml
db/
serverless.yml
vpc/
serverless.yml

Each one of these shared AWS resources stacks should also be outputting the necessary values to be consumed by the downstream services. An example, the api stack should output the REST API Id and the REST API Root Resource Id. Both are needed for services to reuse a singular AWS API Gateway REST API endpoint.

Here is what this example output section would look like if you were using Serverless Framework Pro (which we highly recommend at Serverless Guru)

org: serverlessguru  # <-- swap out for your org
app: my-app # <-- swap out for your app
service: api
provider:
...
outputs:
ApiId:
Ref: ApiGatewayRestApi
ApiResourceId:
Fn::GetAtt:
- ApiGatewayRestApi
- RootResourceId

Now in my service I’ll be able to add this section which can make use of this section.

org: serverlessguru  # <-- swap out for your org
app: my-app # <-- swap out for your app
service: ...
provider:
...
apiGateway:
restApiId: ${output:${self:custom.n}.ApiId}
restApiRootResourceId: ${output:${self:custom.n}.ApiResourceId}
custom:
n: my-app:${self:provider.stage}:${self:provider.region}:api

Note: I’ve made the variable custom.n named in this way to try and fit the code snippet in cleanly on Medium. These dynamic variable names can get quite long.

services — logical groupings of functionality

Inside our services/ directory is where the magic takes place or at least the majority of our application development.

services/  # <-- logical groupings of functionality
billing/ # <-- arbitrary service
src/ # <-- organizes business logic for cloud function
handlers/ # <-- lambda handlers
invoices/
index.js
collections/
index.js
serverless.yml # <-- reuses shell api

Above you can see we have one service called billing . Inside this service we have two AWS Lambda functions that will be packaged individually invoices and collections .

Inside our serverless.yml file we can see how this might work.

org: serverlessguru  # <-- swap out for your org
app: my-app # <-- swap out for your app
service: serviceA
provider:
stage: ${opt:stage, "dev"}
region: ${opt:region, "us-west-2"}
apiGateway:
restApiId: ${output:${self:custom.n}.ApiId}
restApiRootResourceId: ${output:${self:custom.n}.ApiResourceId}
custom:
n: my-app:${self:provider.stage}:${self:provider.region}:api
base: ${self:service}-${self:provider.stage}
# Package the lambda functions individually by subfolder
package:
individually: true
exclude:
- ./**
functions:
invoices:
name: ${self:custom.base}-invoices
handler: index.handler
events:
- http:
path: /invoices
method: ANY
cors: true
package:
include:
- src/handlers/invoices/**
- ../lib/**
collections:
name: ${self:custom.base}-collections
handler: index.handler
events:
- http:
path: /collections
method: ANY
cors: true
package:
include:
- src/handlers/collections/**
- ../lib/**

Above we are doing all the setup required to package our AWS Lambda functions individually. However, we are also doing the following:

  • Setting up CORS
  • Passing all paths/methods matching /collections or /invoices to the relevant AWS Lambda function while keeping some isolation
  • Using a consistent naming convention

Next Steps

If you want to learn more about serverless, we’d highly recommend you check out more of our articles or other content.

Why Serverless Guru?

Serverless Guru exists to be a change agent and overall guide to companies around the globe whom are moving to serverless at scale. We help companies understand where serverless fits and where it doesn’t. Then we lay a proven roadmap to move them along in their serverless journey whether they are just getting started or already developing production serverless applications. We are a team full of serverless experts and the #1 Serverless Development partner. You can contact our sales team at sales@serverlessguru.com.

Serverless + Serverless Guru — Partnership

What did we miss?

When you leave your answer make sure to either comment below or tweet your answer to @serverlessgurux on Twitter because then we can quickly get back to you!

Ryan Jones

Founder, CEO/CTO — Serverless Guru

LinkedIn — @ryanjonesirl

Twitter — @ryanjonesirl

Thanks for reading 😃

If you would like to learn more about Serverless Guru, please follow us on Medium, Twitter, Instagram, Facebook, or LinkedIn!

Serverless Guru

Serverless Guru exists to be a change agent and overall…

Serverless Guru

Written by

We can help you migrate to serverless, build serverless applications, and train your team on serverless best practices. https://www.serverlessguru.com

Serverless Guru

Serverless Guru exists to be a change agent and overall guide to companies around the globe whom are moving to serverless at scale. We help companies understand where serverless fits and where it doesn’t. Then we lay a proven roadmap to move them along in their serverless journey

More From Medium

More from Serverless Guru

More on Cloud Computing from Serverless Guru

More on Cloud Computing from Serverless Guru

Debug AWS Lambda functions with Thundra Online Debugging

More on Cloud Computing from Serverless Guru

More on Cloud Computing from Serverless Guru

Amazon API Gateway HTTP APIs with the Serverless Framework

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade