Strongly typed, best practice & simple declarative interface for AWS lambda functions
NOTE funcy is in pre-release. We will attempt to keep the API as-is, however there may be breaking changes during this period.
funcy provides a TypeScript-first (fully typed), best practice & simple functional interface for AWS lambda functions. The perfect complement to serverless frameworks such as the serverless, SST, SAM, etc.
Features:
- Strongly typed request and response models (inferred or explicit)
- Best practices pipeline with sensible defaults for CORS, security headers, encoding, error handling, metrics, logging, and profiling out of the box.
- A fully-configurable and batteries-included interface; everything you need is an ctrl+space away.
If you prefer lightweight lambda functions and don't want a full-blown framework (like NestJS), then this is for you.
The example below shows parsing, validating and inferring a strongly-typed model for both request and response.
import { api, res } from '@refactorthis/funcy'
import { CreateCustomerRequest, CreateCustomerResponse } from './dtos'
// create customer handler
export const create = api({
parser: {
request: CreateCustomerRequest, // zod or yup schema
response: CreateCustomerResponse, // zod or yup schema
},
handler: async ({ request }) = {
const response = await Customer.create(request)
return res.ok(response)
}
})
You'll notice that the handler itself does not contain any parsing, validation or error handling logic, we adhere to the single-responsibility-principle here and handle all other aspects in middleware, configurable at either api or handler level.
As you can see above, the TypeScript type is inferred at compile time, allowing strong typing of all request and response parameters to your API, almost by magic.
funcy also has a full middleware pipeline, allowing you to control things like CORS, content negotiation, encoding, security header best practices, etc. all out of the box. All you need to do is ctrl+space to see the options. Options can either be set as default for the whole api, or overridden for each handler
pnpm add @refactorthis/funcy
#bun add @refactorthis/funcy
#yarn add @refactorthis/funcy
#npm install --save @refactorthis/funcy
If you haven't already installed your validation framework, add one to your package.
pnpm add zod
# or
# pnpm add yup
To get started let's create a simple API Gateway Proxy (HTTP or REST) lambda handler. This will validate the request and the response with our predefined zod schemas.
import { api, res } from '@refactorthis/funcy'
import { MyRequest, MyResponse } from './dtos'
export const handler = api({
parser: {
request: MyRequest, // zod or yup schema
response: MyResponse, // zod or yup schema
},
handler: async({ request }) = {
// request is the strongly typed request body
// response is also validated and strongly typed
return res.ok(response)
}
})
You can create your own api handlers using api-level defaults. For instance, let's use a custom authorizer context, with strongly typed claims, for use across all of our api.
// my-api.ts
import { createApi } from '@refactorthis/funcy'
// my authorizer struct
interface AuthorizerType {
jwt: { claims: { tenantId: 'string' } }
}
// Create this once for your API and share it
// We will set the authorizer to use, and allow a cross-origin domain
export const api = createApi<AuthorizerType>({
http: { cors: { origin: 'web.mydomain.com' } },
})
// customers-create.ts
import { res } from '@refactorthis/funcy'
import { api } from './my-api'
export const handler = api({
parser: {
request: MyRequestSchema,
response: MyResponseSchema,
path: PathSchema,
query: QueryStringSchema,
},
handler: async({ request, path, query, authorizer }) = {
// request is the strongly typed request body
// path is the strongly typed url path
// query is the strongly typed query string
// authorizer is the strongly typed authorizer context as specified in my-api.ts
// responseType is the strongly typed response type
return res.ok(responseType)
}
})
All handlers using this api will return the appropriate CORS headers, as specified.
Let's create CRUD handlers for the "Customer" domain, with request validation.
import { api, res } from '@refactorthis/funcy'
// create
export const handler = api({
parser: {
request: CreateCustomerRequest,
response: CreateCustomerResponse,
},
handler: async ({ request }) = {
const response = await Customers.create(request)
return res.created(response)
}
})
// get
export const handler = api({
parser: {
path: GetResourcePath,
response: GetCustomerResponse,
},
handler: async ({ request, path }) = {
const response = await Customers.get(path.id)
return res.ok(response)
}
})
// list
export const handler = api({
parser: {
query: ListQueryStringValidator,
},
handler: async ({ request, query }) = {
const { skip, take } = query
const items = await Customers.list(skip, take)
return res.ok({ items, skip, take })
}
})
// update
export const handler = api({
parser: {
request: UpdateCustomerRequest,
response: UpdateCustomerResponse,
path: GetResourcePath,
},
handler: async ({ request, path }) = {
const response = await Customers.update(path.id, request)
return res.ok(response)
}
})
funcy is built to be agnostic of schema library. At this time funcy supports:
funcy Nest.js Express Koa
- Support for other schema libraries
- Verify support for legacy API Gateway proxy integration (< v2)
- Performance test comparison with nest.js, raw lambda, etc.
- Other lambda integrations (s3, dynamo, etc)
- hateoas middleware
- router middleware for proxy+ calls
Working with zod:
- zod - a great Typescript-first validation framework, which can infer your DTO types automatically
- zod-to-openapi - generate your Open API definition code-first from zod schemas.
- openapi-zod-client - alternatively, generate your code from your design-first Open API definition
Working with yup:
- yup - Dead simple Object schema validation
- openapi-yup-generator - Generate your yup schemas from Open API definition
- middy.js - powers the funcy pipeline with it's extensible middleware framework.
- TypeScript - strong-typing is critical for maintainability and reducing bugs.