Backend Guide
The backend consists of AWS Lambda functions managed by Serverless Framework 3, located at platform/backend/.
Project Structure
platform/backend/
├── serverless.yml # Infrastructure and function definitions
├── package.json
├── src/
│ ├── handlers/ # Lambda handler entry points
│ │ ├── getPresignedUrl.js
│ │ ├── submitIntake.js
│ │ ├── processSubmission.js
│ │ ├── documentAnalyzer.js
│ │ ├── quoteCalculator.js
│ │ ├── pipedriveClient.js
│ │ ├── getSubmission.js
│ │ ├── tokenGenerator.js
│ │ └── webhookHandler.js
│ └── lib/ # Shared utilities
│ ├── dynamodb.js
│ ├── s3.js
│ └── ssm.js
└── test/
└── events/ # Test event JSON files for local invocation
serverless.yml Structure
The serverless.yml file defines the service, provider configuration, functions, and resources.
Provider Section
service: dissertation-editor-backend
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
stage: dev
environment:
SUBMISSIONS_TABLE: submissions-${self:provider.stage}
CONFIG_TABLE: config-${self:provider.stage}
UPLOAD_BUCKET: dissertation-editor-uploads-${self:provider.stage}
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:Query
Resource:
- arn:aws:dynamodb:${self:provider.region}:*:table/submissions-*
- arn:aws:dynamodb:${self:provider.region}:*:table/config-*
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
Resource: arn:aws:s3:::dissertation-editor-uploads-*/*
- Effect: Allow
Action:
- ssm:GetParameter
Resource: arn:aws:ssm:${self:provider.region}:*:parameter/dissertation-editor/*
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: arn:aws:lambda:${self:provider.region}:*:function:${self:service}-*
Function Definition Pattern
Each function maps to an HTTP event on API Gateway:
functions:
submitIntake:
handler: src/handlers/submitIntake.handler
timeout: 10
memorySize: 256
events:
- http:
path: intake/submit
method: post
cors: true
For functions that are invoked asynchronously by other Lambdas (not by API Gateway), omit the events section:
documentAnalyzer:
handler: src/handlers/documentAnalyzer.handler
timeout: 30
memorySize: 512
Lambda Handler Pattern
All handlers follow this pattern:
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, PutCommand } = require('@aws-sdk/lib-dynamodb');
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
module.exports.handler = async (event) => {
try {
const body = JSON.parse(event.body || '{}');
// Business logic here
return {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
body: JSON.stringify({ /* response data */ }),
};
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
body: JSON.stringify({ error: 'Internal server error' }),
};
}
};
Key conventions:
- CORS headers are included in every response (API Gateway CORS config handles OPTIONS, but Lambda must set headers on actual responses).
event.bodyis always parsed from JSON string.- Errors are caught and returned as 500 with a generic message. Detailed errors go to
console.errorwhich writes to CloudWatch. - SDK clients are instantiated outside the handler for connection reuse across warm invocations.
Async Invocation Pattern
submitIntake invokes processSubmission asynchronously so the user gets an immediate response:
const { LambdaClient, InvokeCommand } = require('@aws-sdk/client-lambda');
const lambdaClient = new LambdaClient({});
// Inside the handler:
await lambdaClient.send(new InvokeCommand({
FunctionName: process.env.PROCESS_SUBMISSION_FUNCTION,
InvocationType: 'Event', // async -- returns immediately
Payload: JSON.stringify({
submissionId,
s3Key,
}),
}));
The function name is passed via environment variable in serverless.yml:
submitIntake:
handler: src/handlers/submitIntake.handler
environment:
PROCESS_SUBMISSION_FUNCTION: ${self:service}-${self:provider.stage}-processSubmission
Adding a New Function
-
Create the handler file in
src/handlers/:// src/handlers/myNewFunction.js module.exports.handler = async (event) => { // ... }; -
Add the function to
serverless.yml:functions: myNewFunction: handler: src/handlers/myNewFunction.handler timeout: 10 memorySize: 256 events: - http: path: my/endpoint method: get cors: true -
If the function needs additional IAM permissions beyond what the shared role provides, add them to the provider IAM statements.
-
Deploy:
AWS_SDK_LOAD_CONFIG=1 npx serverless deploy --aws-profile dissertation-editor
IAM Permissions
The shared IAM role grants all functions access to:
- DynamoDB:
GetItem,PutItem,UpdateItem,Queryon submissions and config tables - S3:
GetObject,PutObjecton the uploads bucket - SSM:
GetParameteron/dissertation-editor/*parameters - Lambda:
InvokeFunctionon all functions in this service (for async invocation)
If a function needs permissions to a resource not listed above (e.g., SES for email), add a new IAM statement in the provider section.