How I Deployed an Express + Prisma + Supabase API on AWS Lambda Using Serverless Framework
Deploying a full-featured Express API on AWS Lambda with Prisma and Supabase integration can be tricky — especially when you want everything to run serverlessly, scale seamlessly, and stay cost-efficient.
In this guide, I’ll walk you through the exact steps I took to deploy my TypeScript Express API to AWS Lambda using the Serverless Framework, integrating Prisma ORM, Supabase, and Amazon S3 for file storage.
🧰 Tech Stack Overview
Here’s what we’ll use:
- Node.js (TypeScript) — backend runtime
- Express.js — API framework
- Prisma ORM — database access and migrations
- Supabase (PostgreSQL) — database provider
- AWS S3 — file uploads
- AWS Lambda + API Gateway — serverless deployment
- Serverless Framework — deployment and configuration tool
📁 Project Structure
Our project structure looks like this:
├── src
│ ├── controllers/
│ ├── middlewares/
│ ├── prisma/
│ ├── app.ts
│ └── index.ts
├── serverless.yml
├── package.json
└── .env
⚙️ Step 1. Initialize the Project
Start a new Node project and install dependencies:
npm init -y
npm install express cors dotenv prisma @prisma/client
npm install serverless serverless-http serverless-offline serverless-esbuild
Then initialize Prisma:
npx prisma init
Update your Prisma schema (prisma/schema.prisma):
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
🧩 Step 2. Connect Prisma to Supabase
Go to your Supabase Dashboard → Project Settings → Database → Connection string → Node.js, and copy the connection string.
Use the connection pooling option for Lambda functions:
DATABASE_URL="postgresql://postgres.<user_id>:<password>@aws-0-us-east-1.pooler.supabase.com:6543/postgres?pgbouncer=true"
⚠️ Important:
If your password contains special characters (like@,#, or$), wrap it inencodeURIComponent()before using it, or the connection will fail.
🚀 Step 3. Create the Express App
Here’s the main Express setup in src/app.ts:
import express from 'express';
import cors from 'cors';
import { register, login } from './controllers/auth.controller';
import { uploadFile, listFiles, deleteFile } from './controllers/file.controller';
import { authMiddleware } from './middlewares/auth.middleware';
import { errorMiddleware } from './middlewares/error.middleware';
export const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// Auth routes
app.post('/api/register', register);
app.post('/api/login', login);
// File routes
app.post('/api/files', authMiddleware, uploadFile);
app.get('/api/files', authMiddleware, listFiles);
app.delete('/api/files/:id', authMiddleware, deleteFile);
// Error handling
app.use(errorMiddleware);
🧱 Step 4. Wrap Express with Serverless HTTP
To make Express compatible with AWS Lambda, use the serverless-http package.
// src/index.ts
import serverless from 'serverless-http';
import { app } from './app';
export const handler = serverless(app);
📦 Step 5. Configure Serverless Framework
Your serverless.yml defines how AWS will run and expose your Lambda function:
service: backend-api
frameworkVersion: "4"
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stage: dev
environment:
JWT_SECRET: ${env:JWT_SECRET}
DATABASE_URL: ${env:DATABASE_URL}
AWS_S3_BUCKET: ${env:AWS_S3_BUCKET}
AWS_REGION_L: ${env:AWS_REGION_L}
functions:
app:
handler: src/index.handler
events:
- httpApi: "*"
plugins:
- serverless-esbuild
- serverless-offline
package:
patterns:
- "!node_modules/.prisma/client/query_engine-*"
- "node_modules/.prisma/client/libquery_engine-rhel-openssl-3.0.x.so.node"
Key Points
httpApi: "*" tells API Gateway to route all requests to your Express app (no need to manually add routes).
Prisma binary inclusion ensures the right engine is packaged for AWS Lambda (Linux environment).
🌍 Step 6. Deploy to AWS
Deploy with one command:
npx serverless deploy
Once complete, you’ll see an output similar to this:
✔ Service deployed to AWS
endpoint: https://xyz123.execute-api.us-east-1.amazonaws.com
functions:
app: backend-api-dev-app
Now you can hit:
https://xyz123.execute-api.us-east-1.amazonaws.com/api/[route-name]
or any other route you defined.
Keep only the
$defaultroute in API Gateway — it acts as a catch-all for your Express routes.
🧠 Step 7. Common Issues (and How I Solved Them)
| Issue | Cause | Fix |
|---|---|---|
| Invalid DB URL | Password had unescaped characters | Used encodeURIComponent()
|
| Empty database warning | No tables created in Supabase | Added at least one table before introspection |
| Prisma binary missing | Local OS mismatch | Included Linux binary in serverless.yml
|
| 404 routes | Missing $default route in API Gateway |
Configured httpApi: "*"
|
| esbuild “Invalid option” error | Mismatch in plugin configuration | Switched to serverless-esbuild
|
🔍 Step 8. Test the Deployment
Use Postman or curl to test endpoints:
curl -X POST https://xyz123.execute-api.us-east-1.amazonaws.com/api/register \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "password": "secret"}'
If everything’s configured correctly, you should see your API responding from AWS Lambda!
✅ Conclusion
Deploying an Express + Prisma + Supabase backend on AWS Lambda using the Serverless Framework provides:
- Cost-efficient, pay-per-execution architecture
- Automatic scaling
- No server maintenance
- Easy integration with AWS services like S3
Through this process, I learned how crucial it is to manage environment variables, match Prisma binaries, and configure
$defaultroutes for Express APIs on API Gateway.
📚 Next Steps
You can extend this setup by:
- Adding CI/CD with GitHub Actions
- Using AWS Secrets Manager for credentials
- Integrating CloudWatch for logging and metrics
- Implementing signed S3 URLs for secure uploads
Top comments (0)