Overview

In this guide, you will set up and deploy a serverless Node.js REST API to AWS Lambda using the Serverless Framework.

AWS Lambda is part of the AWS cloud platform and allows you to use the serverless paradigm to run your code without managing servers. To deploy a REST API to AWS Lambda, you need to make use of several additional AWS services, namely S3 to host the files and API Gateway to expose the API over HTTP.

The Serverless Framework simplifies the deployment to Lambda with a CLI that helps with workflow automation and AWS resource provisioning.

The REST API will use Prisma Client to handle fetching, creating, and deleting records from a database. Specifically, each function will represent a REST resource endpoint and use Prisma Client to handle database operations against a PostgreSQL database (e.g. hosted on Heroku).

The focus of this guide is showing how an API based on Prisma can be deployed to AWS Lambda. The starting point will the Prisma AWS example which has a couple of REST endpoints preconfigured as serverless functions.

Throughout the guide you'll find various checkpoints that enable you to validate whether you performed the steps correctly.

A note on deploying GraphQL servers to AWS Lambda

While the example uses REST, the same principles apply to a GraphQL server, with the main difference being that you typically only need a single function to serve a GraphQL API.

In that function, the context.callbackWaitsForEmptyEventLoop of the AWS Lambda Context Object needs to be set to false as follows:

1exports.server = (event, context, cb) => {
2 // Set to false to send the response right away when the callback executes, instead of waiting for the Node.js event loop to be empty.
3 context.callbackWaitsForEmptyEventLoop = false
4
5 return lambda.graphqlHandler(event, context, cb)
6}

Prerequisites

  • Hosted PostgreSQL database and a URL from which it can be accessed, e.g. postgresql://username:password@your_postgres_db.cloud.com/db_identifier (you can use Heroku, which offers a free plan).
  • AWS account and a corresponding access key for programmatic access.
  • Serverless Framework CLI installed.
  • Node.js installed.
  • PostgreSQL CLI psql installed.

Prisma workflow

Prisma supports different workflows depending on whether you integrate with an existing database or create a new one from scratch. Regardless of the workflow, Prisma relies on the Prisma schema, i.e. schema.prisma file.

This guide starts with an empty database created with plain SQL and looks as follows:

  1. Define the database schema using SQL.
  2. Run prisma introspect locally which will introspect and populate the schema.prisma with models based on the database schema.
  3. Run prisma generate which will generate Prisma Client based on the Prisma schema.

1. Download the example

Open your terminal and navigate to a location of your choice. Create the directory that will hold the application code and download the example code:

$mkdir prisma-aws-lambda
$cd prisma-aws-lambda
$curl https://codeload.github.com/prisma/prisma-examples/tar.gz/master | tar -xz --strip=3 prisma-examples-master/deployment-platforms/aws-lambda/

Checkpoint: ls -1 should show:

$ls -1
$README.md
$aws-credentials
$handlers
$node_modules
$package-lock.json
$package.json
$prisma
$schema.sql
$serverless.yml

After downloading the example code, install the dependencies:

1npm install

2. Set the DATABASE_URL environment variable locally

Set the DATABASE_URL environment variable locally so you can create the database schema and Prisma can access the database to introspect:

$export DATABASE_URL="postgresql://__USER__:__PASSWORD__@__HOST__/__DATABASE__"

Note: you will need the DATABASE_URL environment variable for the subsequent steps. Set it in all terminal sessions related to this project.

You need to replace the uppercase placeholders with your database credentials, e.g.:

$postgresql://janedoe:randompassword@yourpostgres.compute-1.amazonaws.com:5432/yourdbname

3. Set the DATABASE_URL in .env

For the Lambda functions to access the database, they need access to the DATABASE_URL environment variable.

For this, you need to define a .env file which the preconfigured serverless-dotenv-plugin will use to inject into the function runtime.

The repository contains a .env.example example file to assist with this.

Copy the file:

1cp .env.example .env

Then open the .env file and set the DATABASE_URL with the same value as in step 2.

Note: When working with Git repositories, it's best practice to keep secrets, e.g. DATABASE_URL out of the repository. This is typically done by adding a line to .gitignore to ignore the .env file. This guide only copies the source without creating a repository, so this is not necessary unless you initialize a repository.

4. Create the database schema

To create your database schema, run the schema.sql from the example code as follows:

$psql $DATABASE_URL -f schema.sql

Checkpoint: psql $DATABASE_URL -c "\dt" should show the list of tables:

$ List of relations
$ Schema | Name | Type | Owner
$ --------+---------+-------+----------------
$ public | Post | table | janedoe
$ public | Profile | table | janedoe
$ public | User | table | janedoe

Congratulations, you have successfully created the database schema.

5. Introspect the database

Introspect the database with the Prisma CLI:

$npx prisma introspect

Prisma will introspect the database defined in the datasource block of the Prisma schema and populate the Prisma schema with models corresponding to the database tables.

Checkpoint: prisma/schema.prisma should look as follows (note that the fields on the models have been reordered for better readability):

1model Post {
2 id Int @default(autoincrement()) @id
3 createdAt DateTime @default(now())
4 title String
5 content String?
6 published Boolean @default(false)
7 User User @relation(fields: [authorId], references: [id]) // relation field
8 authorId Int // relation scalar field
9}
10
11model Profile {
12 id Int @default(autoincrement()) @id
13 bio String?
14 userId Int @unique // relation scalar field
15 User User @relation(fields: [userId], references: [id]) // relation field
16}
17
18model User {
19 id Int @default(autoincrement()) @id
20 email String @unique
21 name String?
22 Post Post[] // relation field
23 Profile Profile? // relation field
24}

Rename the relation fields for easy access

Because both the generated Post and Profile fields in the User model are virtual (i.e. they're not backed by a foreign key in the database), you can manually rename them in your Prisma schema. This will only affect the generated client and is typically done so that they have a more meaningful name in the context of the relation.

In the resulting Prisma schema there are two types of relation fields:

  • Relation fields: identified by having a Model name as the type, e.g. the User field in the Post model. Can be renamed to better fit its usage, e.g. User -> author.
  • Relation scalar fields: these are used to store the foreign key, e.g. the authorId field in the Post model. Cannot be rename as it must match the field in the database.

The names of the relation fields are used in the client to access those relations for example, fetching a specific Post and its associated User object would as follows with the Prisma schema above:

1const postAuthor = await prisma.post.findOne({
2 where: { id: 1 },
3 include: { User: true },
4})

If you rename the User field in the Post model to author, you'll be able to access it as follows:

1const postAuthor = await prisma.post.findOne({
2 where: { id: 1 },
3 include: { author: true },
4})

Based on that logic, rename the relation fields to better adhere to the naming conventions:

1model Post {
2 id Int @default(autoincrement()) @id
3 createdAt DateTime @default(now())
4 title String
5 content String?
6 published Boolean @default(false)
7 author User @relation(fields: [authorId], references: [id]) // renamed from `User` -> `author`
8 authorId Int // relation scalar field
9}
10
11model Profile {
12 id Int @default(autoincrement()) @id
13 bio String?
14 userId Int @unique // relation scalar field
15 user User @relation(fields: [userId], references: [id]) // renamed from `User` -> `user`
16}
17
18model User {
19 id Int @default(autoincrement()) @id
20 email String @unique
21 name String?
22 posts Post[] // renamed from `Post` -> `posts`
23 profile Profile? // renamed from `User` -> `profile`
24}

6. Set the AWS Access key as an environment variable

In order for the Serverless Framework to provision the AWS resources and deploy your application, you need to configure the access key.

There are different ways to get the access key, depending on whether you create you use your personal account or create a special IAM user for the Serverless Framework (this approach is recommended for security reasons as it allows to set granular permissions). To get an access key for your account, follow the AWS guide

Once you have the Access key ID and a Secret access key. Set them with the following command:

1serverless config credentials --provider aws --key AWS_ACCESS_KEY_ID --secret AWS_SECRET_ACCESS_KEY

7. Deploy the app

Your project is now ready for deployment:

1serverless deploy

Serverless will create the AWS resources and upload your code, and output the service information:

1Service Information
2service: prisma-aws-lambda-example
3stage: dev
4region: us-east-1
5stack: prisma-aws-lambda-example-dev
6resources: 39
7api keys:
8 None
9endpoints:
10 GET - https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/
11 GET - https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/seed
12 GET - https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/users
13 POST - https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/users
14 GET - https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/posts
15functions:
16 status: prisma-aws-lambda-example-dev-status
17 seed: prisma-aws-lambda-example-dev-seed
18 getUsers: prisma-aws-lambda-example-dev-getUsers
19 createUser: prisma-aws-lambda-example-dev-createUser
20 getPosts: prisma-aws-lambda-example-dev-getPosts
21layers:
22 None

Checkpoint: Call the status endpoint

$curl https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/

The call should return: {"up":true}

8. Test your deployed REST API

With the API base URL: https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/, you can test the API's endpoints:

EndpointDescriptionImplementation
GET /Statushandlers/status.js
GET /seedDelete all database records and seed the database with test users, profiles, and posts. Returns the created users.handlers/seed.js
GET /usersFetch all users in the database with their related profileshandlers/users.js
POST /usersCreate a single users in the databasehandlers/create-user.js
GET /postsFetch all posts and their related authorshandlers/posts.js

To call the API, you can use curl:

1curl -v https://UNIQUE_IDENTIFIER.execute-api.us-east-1.amazonaws.com/dev/seed

Notes

serverless.yaml

The serverless.yml configuration file is where the endpoint and function configuration lives. You can update this file to add or change endpoints. For more AWS specific configuration, check out the AWS provider configuration in the Serverless Framework Docs.

Binary targets in schema.prisma

The Prisma schema contains the following in the generator block:

1binaryTargets = ["native", "rhel-openssl-1.0.x"]

This is necessary as the local runtime is different to the Lambda runtime. Adding the binaryTarget will ensure that the compatible Prisma Engine binary is available.

Summary

Congratulations! You have successfully deployed the API to AWS Lambda.

For more insight into Prisma Client's API, look at the function handlers in the handlers/ folder.

Generally, when using a FaaS (function as a service) environment to interact with a database, it's beneficial to pool DB connections for performance reasons. This is because every function invocation may result in a new connection to the database (this is not a problem with a constantly running node.js server). For more information on some of the solutions, you may want to look at our general FaaS guide.

Edit this page on Github