Serverless Functions (API Endpoints)
You can think of serverless functions as API Endpoints, and in the future we'll update the terminology used.
Originally, Redwood apps were intended to be deployed as serverless functions to AWS Lambda. Whenever a Redwood app is deployed to a "serverful" environment such as Fly or Render, a Fastify server is started and your Redwood app's functions in api/src/functions
are automatically registered onto the server. Request adapters are also automatically configured to handle the translation between Fastify's request and reply objects to the functions' AWS Lambda signature.
Redwood looks for serverless functions in api/src/functions
. Each function is mapped to a URI based on its filename. For example, you can find api/src/functions/graphql.js
at http://localhost:8911/graphql
.
Creating Serverless Functions
Creating serverless functions is easy with Redwood's function generator:
yarn rw g function <name>
This will generate a stub serverless function in the folder api/src/functions/<name>
, along with a test and an empty scenarios file.
Example of a bare minimum handler you need to get going:
export const handler = async (event, context) => {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data: '${name} function',
}),
}
}
The handler
For a lambda function to be a lambda function, it must export a handler that returns a status code. The handler receives two arguments: event
and context
. Whatever it returns is the response
, which should include a statusCode
at the very least.
File/Folder Structure
For example, with a target function endpoint name of /hello, you could save the function file in one of the following ways:
./api/src/functions/hello.ts
./api/src/functions/hello/hello.ts
./api/src/functions/hello/index.ts
Other files in the folder will not be exposed as an endpoint
Re-using/Sharing code
You can use code in api/src
in your serverless function, some examples:
// importing `db` directly
import { db } from 'src/lib/db'
// importing services
import { update } from 'src/services/subscriptions'
// importing a custom shared library
import { reportError } from 'src/lib/errorHandling'
If you just want to move some logic into another file, that's totally fine too!
api/src
├── functions
│ ├── graphql.ts
│ └── helloWorld
│ ├── helloWorld.scenarios.ts
│ ├── helloWorld.test.ts
│ └── helloWorld.ts # <-- imports hellWorldLib
│ └── helloWorldLib.ts # <-- exports can be used in the helloWorld
Developing locally
When you run yarn rw dev
- it'll watch for changes and make your functions available at:
localhost:8911/{functionName}
andlocalhost:8910/.redwood/functions/{functionName}
(used by the web side).
Note that the .redwood/functions
path is determined by your setting in your redwood.toml - and is used both in development and in the deployed Redwood app
Testing
You can write tests and scenarios for your serverless functions very much like you would for services, but it's important to properly mock the information that the function handler
needs.
To help you mock the event
and context
information, we've provided several api testing fixture utilities:
Mock | Usage |
---|---|
mockHttpEvent | Use this to mock out the http request event that is received by your function in unit tests. Here you can set headers , httpMethod , queryStringParameters as well as the body and if the body isBase64Encoded . The event contains information from the invoker as JSON-formatted string whose structure will vary. See Working with AWS Lambda proxy integrations for HTTP APIs for the payload format. |
mockContext | Use this function to mock the http context . Your function handler receives a context object with properties that provide information about the invocation, function, and execution environment. See AWS Lambda context object in Node.js for what context properties you can mock. |
mockSignedWebhook | Use this function to mock a signed webhook. This is a specialized mockHttpEvent mock that also signs the payload and adds a signature header needed to verify that the webhook is trustworthy. See How to Receive and Verify an Incoming Webhook to learn more about signing and verifying webhooks. |
How to Test Serverless Functions
Let's learn how to test a serverless function by first creating a simple function that divides two numbers.
As with all serverless lambda functions, the handler accepts an APIGatewayEvent
which contains information from the invoker.
That means it will have the HTTP headers, the querystring parameters, the method (GET, POST, PUT, etc), cookies, and the body of the request.
See Working with AWS Lambda proxy integrations for HTTP APIs for the payload format.
Let's generate our function:
yarn rw generate function divide
We'll use the querystring to pass the dividend
and divisor
to the function handler on the event as seen here to divide 10 by 2.
// request
http://localhost:8911/divide?dividend=10&divisor=2
If the function can successfully divide the two numbers, the function returns a body payload back in the response with a HTTP 200 Success status:
// response
{"message":"10 / 2 = 5","dividend":"10","divisor":"2","quotient":5}
And, we'll have some error handling to consider the case when either the dividend or divisor is missing and return a HTTP 400 Bad Request status code; or, if we try to divide by zero or something else goes wrong, we return a 500 Internal Server Error.
import type { APIGatewayEvent } from 'aws-lambda'
export const handler = async (event: APIGatewayEvent) => {
// sets the default response
let statusCode = 200
let message = ''
try {
// get the two numbers to divide from the event query string
const { dividend, divisor } = event.queryStringParameters
// make sure the values to divide are provided
if (dividend === undefined || divisor === undefined) {
statusCode = 400
message = `Please specify both a dividend and divisor.`
throw Error(message)
}
// divide the two numbers
const quotient = parseInt(dividend) / parseInt(divisor)
message = `${dividend} / ${divisor} = ${quotient}`
// check if the numbers could be divided
if (!isFinite(quotient)) {
statusCode = 500
message = `Sorry. Could not divide ${dividend} by ${divisor}`
throw Error(message)
}
return {
statusCode,
body: {
message,
dividend,
divisor,
quotient,
},
}
} catch (error) {
return {
statusCode,
body: {
message: error.message,
},
}
}
}
Sure, you could launch a browser or use Curl or some other manual approach and try out various combinations to test the success and error cases, but we want to automate the tests as part of our app's CI.
That means we need to write some tests.
Function Unit Tests
To test a serverless function, you'll work with the test script associated with the function. You'll find it in the same directory as your function:
api
├── src