├── .github └── workflows │ ├── dev-stage.yaml │ └── prod-stage.yaml ├── .gitignore ├── README.md ├── api └── index.js ├── drizzle.config.js ├── package-lock.json ├── package.json ├── public ├── .gitkeep └── index.html ├── reference └── serverless-iam-policy.md ├── serverless-nodejs-api.code-workspace ├── serverless.yml ├── src ├── cli │ ├── migrator.js │ └── putSecrets.js ├── db │ ├── clients.js │ ├── crud.js │ ├── schemas.js │ └── validators.js ├── index.js ├── lib │ └── secrets.js └── migrations │ ├── 0000_common_wolfpack.sql │ ├── 0001_quick_misty_knight.sql │ └── meta │ ├── 0000_snapshot.json │ ├── 0001_snapshot.json │ └── _journal.json └── vercel.json /.github/workflows/dev-stage.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Dev Stage 2 | 3 | on: 4 | push: 5 | branches: [ dev ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | env: 12 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 13 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 14 | API_KEY: ${{ secrets.NEON_API_KEY }} 15 | STAGE: dev 16 | GH_TOKEN: ${{ github.token }} # github cli -> gh 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Use Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: "20.11" 25 | cache: 'npm' 26 | - name: Install dependencies 27 | run: npm install 28 | - name: Install neonctl and tsx 29 | run: npm install -g neonctl tsx 30 | - name: Delete Previous Branch 31 | run: neonctl branches delete dev --api-key ${{ env.API_KEY }} 32 | continue-on-error: true 33 | - name: Create Branched Database 34 | run: neonctl branches create --name dev --api-key ${{ env.API_KEY }} 35 | - name: Branch Connection String in Parameter Store 36 | run: | 37 | export DB_URL=$(neonctl connection-string --branch dev --api-key ${{ env.API_KEY }}) 38 | npx tsx src/cli/putSecrets.js dev $DB_URL 39 | - name: Deploy dev stage 40 | run: | 41 | npm run deploy-dev-stage 42 | - name: Dev Stage Pull Request 43 | run: | 44 | export PR_BRANCH=$(git branch --show-current) 45 | export DEFAULT_BRANCH=$(git remote show origin | awk '/HEAD branch/ {print $NF}') 46 | echo "$PR_BRANCH and $DEFAULT_BRANCH" 47 | export DEV_STAGE_INFO=$(npm run info-dev-stage) 48 | gh pr create --title "Automated PR from Dev Stage" --body "$DEV_STAGE_INFO" --base $DEFAULT_BRANCH --head $PR_BRANCH --repo $GITHUB_REPOSITORY -------------------------------------------------------------------------------- /.github/workflows/prod-stage.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy Production App 2 | 3 | on: 4 | # push: 5 | # branches: [ main ] 6 | pull_request: 7 | types: [ closed ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | if: github.event.pull_request.merged == true 13 | runs-on: ubuntu-latest 14 | env: 15 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 16 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 17 | STAGE: prod 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Use Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: "20.11" 24 | cache: 'npm' 25 | - name: Install dependencies 26 | run: npm install 27 | - name: Deploy 28 | run: npm run deploy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .serverless 3 | .env* 4 | .DS_Store 5 | reference/policy.json 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serverless Node.js API with AWS Lambda & Neon Postgres 2 | -------------------------------------------------------------------------------- /api/index.js: -------------------------------------------------------------------------------- 1 | import { app } from '../src/index' 2 | 3 | export default app -------------------------------------------------------------------------------- /drizzle.config.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | schema: './src/db/schemas.js', 3 | out: './src/migrations' 4 | } 5 | 6 | export default config -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-nodejs-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "dev": "serverless offline --stage dev", 7 | "info": "serverless info --stage prod --region us-east-2", 8 | "deploy": "serverless deploy --stage prod --region us-east-2", 9 | "deploy-dev-stage": "serverless deploy --stage dev --region us-east-2", 10 | "info-dev-stage": "serverless info --stage prod --region us-east-2", 11 | "remove": "serverless remove --stage prod --region us-east-2", 12 | "generate": "drizzle-kit generate:pg --config=drizzle.config.js", 13 | "migrate": "tsx src/cli/migrator.js", 14 | "vercel-build": "echo 'hello'" 15 | }, 16 | "dependencies": { 17 | "@aws-sdk/client-ssm": "^3.499.0", 18 | "@neondatabase/serverless": "^0.7.2", 19 | "drizzle-orm": "^0.29.3", 20 | "express": "^4.18.2", 21 | "serverless-http": "^3.1.1", 22 | "zod": "^3.22.4" 23 | }, 24 | "devDependencies": { 25 | "dotenv": "^16.4.1", 26 | "drizzle-kit": "^0.20.13", 27 | "serverless-dotenv-plugin": "^6.0.0", 28 | "serverless-offline": "^13.3.3", 29 | "tsx": "^4.7.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/serverless-nodejs-api/75acf9d46f09fcba361249f557d4525102918c84/public/.gitkeep -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 |

Hello World

2 |

Env workign?

-------------------------------------------------------------------------------- /reference/serverless-iam-policy.md: -------------------------------------------------------------------------------- 1 | # Serverless Framework IAM Policy 2 | 3 | Use the IAM policy (JSON data) for the [Serverless Framework](https://www.serverless.com/) with the AWS Provider for deploying Node.js apps as serverless functions on AWS Lambda. 4 | 5 | Replace `AWS_ID` with your AWS Account ID (e.g. `123456789`) which you can find under [AWS IAM](https://console.aws.amazon.com/iam/) in the console. 6 | 7 | Use [this gist](https://gist.github.com/codingforentrepreneurs/03f6ddb7ba284e4f82a6c66b3103feda) for the most up-to-date version. 8 | 9 | 10 | `serverless-framework-iam-policy.json` 11 | ```json 12 | { 13 | "Version": "2012-10-17", 14 | "Statement": [ 15 | { 16 | "Effect": "Allow", 17 | "Action": [ 18 | "cloudformation:List*", 19 | "cloudformation:Get*", 20 | "cloudformation:ValidateTemplate", 21 | "ssm:*" 22 | ], 23 | "Resource": [ 24 | "*" 25 | ] 26 | }, 27 | { 28 | "Effect": "Allow", 29 | "Action": [ 30 | "cloudformation:CreateStack", 31 | "cloudformation:CreateUploadBucket", 32 | "cloudformation:DeleteStack", 33 | "cloudformation:Describe*", 34 | "cloudformation:UpdateStack", 35 | "cloudformation:CreateChangeSet", 36 | "cloudformation:ListChangeSets", 37 | "cloudformation:DeleteChangeSet", 38 | "cloudformation:ExecuteChangeSet" 39 | ], 40 | "Resource": [ 41 | "arn:aws:cloudformation:*:AWS_ID:stack/serverless-*" 42 | ] 43 | }, 44 | { 45 | "Effect": "Allow", 46 | "Action": [ 47 | "lambda:Get*", 48 | "lambda:List*", 49 | "lambda:CreateFunction", 50 | "lambda:TagResource", 51 | "lambda:UntagResource" 52 | ], 53 | "Resource": [ 54 | "*" 55 | ] 56 | }, 57 | { 58 | "Effect": "Allow", 59 | "Action": [ 60 | "s3:GetBucketLocation", 61 | "s3:CreateBucket", 62 | "s3:DeleteBucket", 63 | "s3:ListBucket", 64 | "s3:GetBucketPolicy", 65 | "s3:PutBucketPolicy", 66 | "s3:ListBucketVersions", 67 | "s3:PutAccelerateConfiguration", 68 | "s3:GetEncryptionConfiguration", 69 | "s3:PutEncryptionConfiguration", 70 | "s3:DeleteBucketPolicy", 71 | "s3:PutBucketTagging", 72 | "s3:UntagResource", 73 | "s3:TagResource", 74 | "s3:GetBucketTagging", 75 | "s3:ListTagsForResource" 76 | ], 77 | "Resource": [ 78 | "arn:aws:s3:::serverless-*serverlessdeploy*" 79 | ] 80 | }, 81 | { 82 | "Effect": "Allow", 83 | "Action": [ 84 | "s3:PutObject", 85 | "s3:GetObject", 86 | "s3:DeleteObject" 87 | ], 88 | "Resource": [ 89 | "arn:aws:s3:::serverless-*serverlessdeploy*" 90 | ] 91 | }, 92 | { 93 | "Effect": "Allow", 94 | "Action": [ 95 | "lambda:AddPermission", 96 | "lambda:CreateAlias", 97 | "lambda:DeleteFunction", 98 | "lambda:InvokeFunction", 99 | "lambda:PublishVersion", 100 | "lambda:RemovePermission", 101 | "lambda:Update*" 102 | ], 103 | "Resource": [ 104 | "arn:aws:lambda:*:AWS_ID:function:serverless-*" 105 | ] 106 | }, 107 | { 108 | "Effect": "Allow", 109 | "Action": [ 110 | "cloudwatch:GetMetricStatistics" 111 | ], 112 | "Resource": [ 113 | "*" 114 | ] 115 | }, 116 | { 117 | "Action": [ 118 | "logs:CreateLogGroup", 119 | "logs:CreateLogStream", 120 | "logs:DeleteLogGroup", 121 | "logs:TagResource", 122 | "logs:UntagResource" 123 | ], 124 | "Resource": [ 125 | "arn:aws:logs:*:AWS_ID:*" 126 | ], 127 | "Effect": "Allow" 128 | }, 129 | { 130 | "Action": [ 131 | "logs:PutLogEvents" 132 | ], 133 | "Resource": [ 134 | "arn:aws:logs:*:AWS_ID:*" 135 | ], 136 | "Effect": "Allow" 137 | }, 138 | { 139 | "Effect": "Allow", 140 | "Action": [ 141 | "logs:DescribeLogStreams", 142 | "logs:DescribeLogGroups", 143 | "logs:FilterLogEvents" 144 | ], 145 | "Resource": [ 146 | "*" 147 | ] 148 | }, 149 | { 150 | "Effect": "Allow", 151 | "Action": [ 152 | "events:Put*", 153 | "events:Remove*", 154 | "events:Delete*" 155 | ], 156 | "Resource": [ 157 | "arn:aws:events:*:AWS_ID:rule/serverless-*" 158 | ] 159 | }, 160 | { 161 | "Effect": "Allow", 162 | "Action": [ 163 | "events:DescribeRule" 164 | ], 165 | "Resource": [ 166 | "arn:aws:events:*:AWS_ID:rule/serverless-*" 167 | ] 168 | }, 169 | { 170 | "Effect": "Allow", 171 | "Action": [ 172 | "iam:PassRole" 173 | ], 174 | "Resource": [ 175 | "arn:aws:iam::AWS_ID:role/serverless-*" 176 | ] 177 | }, 178 | { 179 | "Effect": "Allow", 180 | "Action": [ 181 | "iam:GetRole", 182 | "iam:CreateRole", 183 | "iam:TagRole", 184 | "iam:PutRolePolicy", 185 | "iam:DeleteRolePolicy", 186 | "iam:DeleteRole" 187 | ], 188 | "Resource": [ 189 | "arn:aws:iam::AWS_ID:role/serverless-*" 190 | ] 191 | }, 192 | { 193 | "Effect": "Allow", 194 | "Action": [ 195 | "apigateway:*" 196 | ], 197 | "Resource": [ 198 | "arn:aws:apigateway:*::/apis*", 199 | "arn:aws:apigateway:*::/restapis*", 200 | "arn:aws:apigateway:*::/apikeys*", 201 | "arn:aws:apigateway:*::/tags*", 202 | "arn:aws:apigateway:*::/usageplans*" 203 | ] 204 | }, 205 | { 206 | "Effect": "Allow", 207 | "Action": [ 208 | "tag:*" 209 | ], 210 | "Resource": [ 211 | "*" 212 | ] 213 | } 214 | ] 215 | } 216 | ``` 217 | -------------------------------------------------------------------------------- /serverless-nodejs-api.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-nodejs-api 2 | frameworkVersion: '3' 3 | useDotenv: true 4 | 5 | provider: 6 | name: aws 7 | runtime: nodejs20.x 8 | environment: 9 | DEBUG: ${env:DEBUG, 0} 10 | STAGE: ${env:STAGE, "prod"} 11 | iam: 12 | role: 13 | name: serverless-my-ssm-role-${env:STAGE, "prod"} 14 | statements: 15 | - Effect: 'Allow' 16 | Resource: '*' 17 | Action: 18 | - "ssm:GetParameter" 19 | - "ssm:GetParameters" 20 | - "ssm:GetParametersByPath" 21 | - "ssm:GetParameterHistory" 22 | - "ssm:DescribeParameters" 23 | 24 | functions: 25 | api: 26 | handler: src/index.handler 27 | events: 28 | - httpApi: '*' 29 | 30 | custom: 31 | dotenv: 32 | exclude: 33 | - AWS_ACCESS_KEY_ID 34 | - AWS_SECRET_ACCESS_KEY 35 | - AWS_SESSION_TOKEN 36 | - DATABASE_URL 37 | 38 | plugins: 39 | - serverless-offline 40 | - serverless-dotenv-plugin 41 | -------------------------------------------------------------------------------- /src/cli/migrator.js: -------------------------------------------------------------------------------- 1 | // tsx src/cli/migrator.js 2 | const { drizzle } = require('drizzle-orm/neon-serverless') 3 | const {migrate} = require('drizzle-orm/postgres-js/migrator') 4 | const schema = require('../db/schemas') 5 | const secrets = require('../lib/secrets') 6 | require('dotenv').config() 7 | 8 | const { Pool, neonConfig } = require('@neondatabase/serverless'); 9 | 10 | const ws = require('ws'); 11 | 12 | async function performMigration() { 13 | const dbUrl = await secrets.getDatabaseUrl() 14 | if (!dbUrl) { 15 | return 16 | } 17 | // neon serverless pool 18 | // https://github.com/neondatabase/serverless?tab=readme-ov-file#pool-and-client 19 | neonConfig.webSocketConstructor = ws; // <-- this is the key bit 20 | const pool = new Pool({ connectionString: dbUrl }); 21 | pool.on('error', err => console.error(err)); // deal with e.g. re-connect 22 | // ... 23 | const client = await pool.connect(); 24 | try { 25 | await client.query('BEGIN'); 26 | const db = await drizzle(client, {schema}) 27 | await migrate(db, {migrationsFolder: 'src/migrations'}) 28 | await client.query('COMMIT'); 29 | } catch (err) { 30 | await client.query('ROLLBACK'); 31 | throw err; 32 | 33 | } finally { 34 | client.release(); 35 | } 36 | await pool.end() 37 | 38 | } 39 | 40 | 41 | if (require.main === module) { 42 | console.log("run Migrations!") 43 | performMigration().then((val)=>{ 44 | console.log("Migrations done") 45 | process.exit(0) 46 | }).catch(err=>{ 47 | console.log('Migrations error') 48 | process.exit(1) 49 | }) 50 | } -------------------------------------------------------------------------------- /src/cli/putSecrets.js: -------------------------------------------------------------------------------- 1 | // github actions cli command 2 | // tsx src/cli/putSecrets.js 3 | const secrets = require('../lib/secrets') 4 | require('dotenv').config() 5 | 6 | 7 | const args = process.argv.slice(2) 8 | 9 | if (args.length !== 2) { 10 | console.log('Usage: tsx src/cli/putSecrets.js ') 11 | process.exit(1) 12 | } 13 | 14 | if (require.main === module) { 15 | console.log("Updating database URL") 16 | const [stage, dbUrl] = args 17 | secrets.putDatabaseUrl(stage, dbUrl).then(val=>{ 18 | console.log(val) 19 | console.log(`Secret set`) 20 | process.exit(0) 21 | }).catch(err=>{ 22 | console.log(`Secret not set ${err}`) 23 | process.exit(1) 24 | }) 25 | } -------------------------------------------------------------------------------- /src/db/clients.js: -------------------------------------------------------------------------------- 1 | 2 | const { neon, neonConfig } = require('@neondatabase/serverless'); 3 | const {drizzle} = require('drizzle-orm/neon-http') 4 | const secrets = require('../lib/secrets') 5 | 6 | async function getDbClient(){ 7 | const dburl = await secrets.getDatabaseUrl() 8 | neonConfig.fetchConnectionCache = true 9 | const sql = neon(dburl); 10 | return sql 11 | } 12 | 13 | async function getDrizzleDbClient(){ 14 | const sql = await getDbClient() 15 | return drizzle(sql) 16 | } 17 | 18 | 19 | module.exports.getDbClient = getDbClient 20 | module.exports.getDrizzleDbClient = getDrizzleDbClient 21 | -------------------------------------------------------------------------------- /src/db/crud.js: -------------------------------------------------------------------------------- 1 | const {desc, eq} = require('drizzle-orm') 2 | const clients = require('./clients') 3 | 4 | const schemas = require('./schemas') 5 | 6 | async function newLead({email}) { 7 | const db = await clients.getDrizzleDbClient() 8 | const result = await db.insert(schemas.LeadTable).values({ 9 | email:email 10 | }).returning() 11 | if (result.length === 1) { 12 | return result[0] 13 | } 14 | return result 15 | } 16 | 17 | async function listLeads() { 18 | const db = await clients.getDrizzleDbClient() 19 | const results = await db.select().from(schemas.LeadTable).orderBy(desc(schemas.LeadTable.createdAt)).limit(10) 20 | return results 21 | } 22 | 23 | 24 | async function getLead(id) { 25 | const db = await clients.getDrizzleDbClient() 26 | const result = await db.select().from(schemas.LeadTable).where(eq(schemas.LeadTable.id, id)) 27 | if (result.length === 1) { 28 | return result[0] 29 | } 30 | return null 31 | } 32 | 33 | module.exports.newLead = newLead 34 | module.exports.listLeads = listLeads 35 | module.exports.getLead = getLead -------------------------------------------------------------------------------- /src/db/schemas.js: -------------------------------------------------------------------------------- 1 | const { serial } = require("drizzle-orm/mysql-core"); 2 | const { text, timestamp, pgTable } = require("drizzle-orm/pg-core"); 3 | 4 | const LeadTable = pgTable('leads', { 5 | id: serial('id').primaryKey().notNull(), 6 | email: text('email').notNull(), 7 | description: text('description').default('This is my comment'), 8 | createdAt: timestamp('created_at').defaultNow(), 9 | }); 10 | 11 | module.exports.LeadTable = LeadTable -------------------------------------------------------------------------------- /src/db/validators.js: -------------------------------------------------------------------------------- 1 | const {z} = require("zod") 2 | 3 | async function validateLead(postData) { 4 | const lead = z.object({ 5 | email: z.string().email() 6 | }) 7 | let hasError; 8 | let validData = {} 9 | let message; 10 | try { 11 | validData = lead.parse(postData) 12 | hasError = false 13 | message = '' 14 | } catch (err) { 15 | console.log(err) 16 | hasError = true 17 | message = "Invalid email, please try again." 18 | } 19 | 20 | return { 21 | data: validData, 22 | hasError: hasError, 23 | message:message 24 | } 25 | 26 | } 27 | 28 | module.exports.validateLead = validateLead -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const serverless = require("serverless-http"); 2 | const express = require("express"); 3 | const crud = require('./db/crud') 4 | const validators = require('./db/validators') 5 | const {getDbClient} = require('./db/clients') 6 | const app = express(); 7 | const STAGE = process.env.STAGE || 'prod' 8 | app.use(express.json()) 9 | 10 | 11 | app.get("/", async (req, res, next) => { 12 | console.log(process.env.DEBUG ) 13 | const sql = await getDbClient() 14 | const now = Date.now() 15 | const [dbNowResult] = await sql`select now();` 16 | const delta = (dbNowResult.now.getTime() - now) / 1000 17 | return res.status(200).json({ 18 | delta: delta, 19 | stage: STAGE 20 | }); 21 | }); 22 | 23 | app.get("/path", (req, res, next) => { 24 | return res.status(200).json({ 25 | message: "Hello from path!", 26 | }); 27 | }); 28 | 29 | 30 | app.get("/api/leads", async (req, res, next) => { 31 | const results = await crud.listLeads() 32 | return res.status(200).json({ 33 | results: results, 34 | }); 35 | }); 36 | 37 | app.post("/api/leads", async (req, res, next) => { 38 | // POST -> create data 39 | const postData = await req.body 40 | // validation??? 41 | const {data, hasError, message} = await validators.validateLead(postData) 42 | if (hasError === true) { 43 | return res.status(400).json({ 44 | message: message ? message : "Invalid request. please try again", 45 | }); 46 | } else if (hasError === undefined) { 47 | return res.status(500).json({ 48 | message: "Server Error", 49 | }); 50 | } 51 | 52 | const result = await crud.newLead(data) 53 | // insert data to the database 54 | return res.status(201).json({ 55 | results: result, 56 | }); 57 | }); 58 | 59 | app.use((req, res, next) => { 60 | return res.status(404).json({ 61 | error: "Not Found", 62 | }); 63 | }); 64 | 65 | // server-full app 66 | // app.listen(3000, ()=>{ 67 | // console.log("running at http://localhost:3000") 68 | // }) 69 | 70 | module.exports.app = app 71 | module.exports.handler = serverless(app); 72 | -------------------------------------------------------------------------------- /src/lib/secrets.js: -------------------------------------------------------------------------------- 1 | const { SSMClient, 2 | GetParameterCommand, 3 | PutParameterCommand, 4 | } = require("@aws-sdk/client-ssm"); 5 | const AWS_REGION='us-east-2' 6 | const STAGE = process.env.STAGE || 'prod' 7 | 8 | async function getDatabaseUrl(){ 9 | const DATABASE_URL_SSM_PARAM=`/serverless-nodejs-api/${STAGE}/database-url` 10 | const client = new SSMClient({region: AWS_REGION}) 11 | const paramStoreData = { 12 | Name: DATABASE_URL_SSM_PARAM, 13 | WithDecryption: true 14 | } 15 | const command = new GetParameterCommand(paramStoreData) 16 | const result = await client.send(command) 17 | return result.Parameter.Value 18 | } 19 | 20 | 21 | async function putDatabaseUrl(stage, dbUrlVal){ 22 | const paramStage = stage ? stage : 'dev' 23 | if (paramStage === 'prod') { 24 | return 25 | } 26 | if (!dbUrlVal) { 27 | return 28 | } 29 | const DATABASE_URL_SSM_PARAM=`/serverless-nodejs-api/${paramStage}/database-url` 30 | const client = new SSMClient({region: AWS_REGION}) 31 | const paramStoreData = { 32 | Name: DATABASE_URL_SSM_PARAM, 33 | Value: dbUrlVal, 34 | Type: "SecureString", 35 | Overwrite: true, 36 | } 37 | const command = new PutParameterCommand(paramStoreData) 38 | const result = await client.send(command) 39 | return result 40 | } 41 | 42 | module.exports.getDatabaseUrl = getDatabaseUrl 43 | module.exports.putDatabaseUrl = putDatabaseUrl -------------------------------------------------------------------------------- /src/migrations/0000_common_wolfpack.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "leads" ( 2 | "id" serial PRIMARY KEY NOT NULL, 3 | "email" text NOT NULL, 4 | "created_at" timestamp DEFAULT now() 5 | ); 6 | -------------------------------------------------------------------------------- /src/migrations/0001_quick_misty_knight.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "leads" ADD COLUMN "description" text DEFAULT 'This is my comment'; -------------------------------------------------------------------------------- /src/migrations/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "eec47f4c-84dc-47a7-883a-ae05d7ac5e1b", 3 | "prevId": "00000000-0000-0000-0000-000000000000", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": { 7 | "leads": { 8 | "name": "leads", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "email": { 18 | "name": "email", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true 22 | }, 23 | "created_at": { 24 | "name": "created_at", 25 | "type": "timestamp", 26 | "primaryKey": false, 27 | "notNull": false, 28 | "default": "now()" 29 | } 30 | }, 31 | "indexes": {}, 32 | "foreignKeys": {}, 33 | "compositePrimaryKeys": {}, 34 | "uniqueConstraints": {} 35 | } 36 | }, 37 | "enums": {}, 38 | "schemas": {}, 39 | "_meta": { 40 | "columns": {}, 41 | "schemas": {}, 42 | "tables": {} 43 | } 44 | } -------------------------------------------------------------------------------- /src/migrations/meta/0001_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "6496c50e-91d2-4869-a6f0-206498627ac4", 3 | "prevId": "eec47f4c-84dc-47a7-883a-ae05d7ac5e1b", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": { 7 | "leads": { 8 | "name": "leads", 9 | "schema": "", 10 | "columns": { 11 | "id": { 12 | "name": "id", 13 | "type": "serial", 14 | "primaryKey": true, 15 | "notNull": true 16 | }, 17 | "email": { 18 | "name": "email", 19 | "type": "text", 20 | "primaryKey": false, 21 | "notNull": true 22 | }, 23 | "description": { 24 | "name": "description", 25 | "type": "text", 26 | "primaryKey": false, 27 | "notNull": false, 28 | "default": "'This is my comment'" 29 | }, 30 | "created_at": { 31 | "name": "created_at", 32 | "type": "timestamp", 33 | "primaryKey": false, 34 | "notNull": false, 35 | "default": "now()" 36 | } 37 | }, 38 | "indexes": {}, 39 | "foreignKeys": {}, 40 | "compositePrimaryKeys": {}, 41 | "uniqueConstraints": {} 42 | } 43 | }, 44 | "enums": {}, 45 | "schemas": {}, 46 | "_meta": { 47 | "columns": {}, 48 | "schemas": {}, 49 | "tables": {} 50 | } 51 | } -------------------------------------------------------------------------------- /src/migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "pg", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "5", 8 | "when": 1706213535629, 9 | "tag": "0000_common_wolfpack", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "5", 15 | "when": 1706214643458, 16 | "tag": "0001_quick_misty_knight", 17 | "breakpoints": true 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/api(.*)", 5 | "destination": "/api" 6 | } 7 | ] 8 | } --------------------------------------------------------------------------------