Fully typed and consistent Serverless Framework configurations, 100% TypeScript, for a world without YAML.
Explore the docs »
View examples
·
Report Bug
·
Request Feature
TL;DR YAML should not exist. https://noyaml.com/
Typed Serverless helps you build a Serverless application using only TypeScript & Serverless Framework. It's a small library to help you build strongly typed serverless.ts
, including AWS CloudFormation resources, logical ids and references between resources, all typed and with an excellent autocomplete.
Serverless Framework is a great framework to create serverless applications, but usually you will see YAML configurations instead of TypeScript. Usually it starts with a very simple serverless.yaml
configuration, but as more features are added, it becomes tricky to ensure that everthing is correctly configured and consistent. In some cases YAML is not enough, and we need some serverless plugin to rescue us.
-
Save time. Many errors that would only happen at runtime can be prevented by using TypeScript. For example, a very common mistake is referencing a wrong ARN resource when defining an IAM Policy.
-
Autocomplete + Type checking for almost everything, including all IDs you define for your resources, and also all AWS CloudFormation resources using Typed AWS.
-
Ensure a consistent style. Avoid hundreds of string interpolations and copy-and-paste by creating helper functions.
-
It reduces the need for some Serverless plugins for simple tasks like conditionals, resource tags, environment validation, and anything else that can be replaced by an imperative programming language.
npm install --save-dev typed-serverless typed-aws serverless @serverless/typescript serverless-esbuild ts-node
Create a serverless.ts
:
import type { AWS } from '@serverless/typescript';
import { TypedServerless, SQS } from 'typed-serverless';
type Ids = 'MyQueue' | 'SendMessageFn';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
service: 'minimal',
plugins: ['serverless-esbuild'],
provider: {
name: 'aws',
runtime: 'nodejs14.x',
region: 'eu-west-1',
lambdaHashingVersion: '20201221',
tags: {
myCustomTag: 'my-sample-tag',
},
iam: {
role: {
statements: [
{
Effect: 'Allow',
Action: 'sqs:*',
Resource: typed.getArn('MyQueue'),
},
],
},
},
},
resources: {
Resources: {
...typed.resources({
'MyQueue': ({ name, awsTags }) =>
SQS.Queue({ QueueName: name, Tags: awsTags}),
}),
},
},
functions: typed.functions({
'SendMessageFn': ({ name }) => ({
name,
handler: './mylambda.handler',
events: [{ http: { method: 'get', path: 'send' } }],
environment: {
QUEUE_URL: typed.ref('MyQueue'),
},
}),
}),
};
module.exports = typed.build(serverlessConfiguration);
Create your first lambda file mylambda.handler
:
import { SQS } from 'aws-sdk';
const sqs = new SQS();
export const handler = async (event) => {
const messageSent = await sqs
.sendMessage({
QueueUrl: process.env.QUEUE_URL,
MessageBody: JSON.stringify({
createdAt: new Date().toISOString(),
event,
}),
})
.promise();
return { statusCode: 200, body: JSON.stringify({ messageSent })};
};
npx sls --stage dev deploy
Used to define your resources.
-
ResourceId
should be a valid string literal type defined atIds
inTypedServerless.createDefault<Ids>()
-
ResourceBuilder
should be a function like(resourceParams) => SomeResouceObject()
.This function will be called with
resourceParams
. If your are usingTypeServerless.createDefault<>()
, it means that yourresourceParams
will be{ name, tags, awsTags }
name
will be'{service}-{stage}-{ResourceId}'
tags
will be the same value you define at your serverlessConfig.provider.tags.awsTags
will be your serverlessConfig.provider.tags converted to an array of{ Key: string, Value: string }
Important: Always use name
in your resource, so this way, Typed Serverless can keep track of any logical id to name mapping. It's important for other features like .getName('ResourceId')
.
Tip: We are using typed-aws
to provide almost all CloudFormation Resource. Please check if your resource is available.
Tip: You can also create your own resourceParams
by providing a custom resourceParamsFactory
through TypedServerless.create<Ids>({ resourceParamsFactory })
. Check out our default implementation.
E.g.
type Ids = 'MyQueue' | 'SomeOtherResource';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
...
Resources: typed.resources({
'MyQueue': ({ name }) =>
SQS.Queue({ QueueName: name }), // Always use the provided name
'SomeOtherResource': ...
})
...
}
Use this function to make a CloudFormation reference to a resource. E.g. {'Ref': 'SomeResourceId'}
.
ResourceId
should be a valid string literal type defined atIds
inTypedServerless.createDefault<Ids>()
Important: Every CloudFormation resource has a differente return value for a Ref
property. E.g. SQS Queues Ref
property returns the queue URL.
Note: This function will also validate if you are referencing a defined resource using resources({ ... })
type Ids = 'MyQueue' | 'SendMessageFn';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
...
resources: {
Resources: typed.resources({
'MyQueue': ({ name }) =>
SQS.Queue({ QueueName: name }),
}),
},
functions: typed.functions({
'SendMessageFn': ({ name }) => ({
name,
handler: './mylambda.handler',
events: [{ http: { method: 'get', path: 'send' } }],
environment: {
QUEUE_URL: typed.ref('MyQueue'),
},
}),
}),
...
}
Use this function to make a CloudFormation reference to a resource ARN
. E.g. {'Fn::GetAtt': ['SomeResourceId', 'Arn']}
.
ResourceId
should be a valid string literal type defined atIds
inTypedServerless.createDefault<Ids>()
Note: This function will also validate if you are referencing a defined resource using resources({ ... })
type Ids = 'MyQueue' | 'MyDeadLetterQueue';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
...
Resources: typed.resources({
'MyQueue': ({ name }) =>
SQS.Queue({
QueueName: name,
RedrivePolicy: {
deadLetterTargetArn: typed.arn('MyDeadLetterQueue'),
maxReceiveCount: 3,
},
}),
'MyDeadLetterQueue': ({ name }) =>
SQS.Queue({ QueueName: name }),
})
...
}
Use this function to get a resource name.
ResourceId
should be a valid string literal type defined atIds
inTypedServerless.createDefault<Ids>()
Important:
Note: This function will also validate if you are referencing a defined resource using resources({ ... })
type Ids = 'MyQueue' | 'MyDeadLetterQueue';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
...
Resources: typed.resources({
'MyQueue': ({ name }) =>
SQS.Queue({
QueueName: name,
RedrivePolicy: {
deadLetterTargetArn: typed.arn('MyDeadLetterQueue'),
maxReceiveCount: 3,
},
}),
'MyDeadLetterQueue': ({ name }) =>
SQS.Queue({ QueueName: name }),
})
...
}
Use this function to be able to serialize an object to a JSON string when you also want to support CloudFormation expressions evaluation inside the object.
The main use case for this is to overcome a limitation in CloudFormation that does not allow using CloudFormation intrinsic functions (like Fn::Get, Ref, Fn::*) in a JSON string. This is common when creating a AWS CloudWatch Dashboard, a Step Function State Machine, and other places.
anyObject
should be any valid TypeScript object.
type Ids = 'MyQueue' | 'SendMessageFn';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
...
resources: {
Resources: typed.resources({
'MyQueue': ({ name }) =>
SQS.Queue({ QueueName: name }),
}),
},
functions: typed.functions({
'SendMessageFn': ({ name }) => ({
name,
handler: './mylambda.handler',
events: [{ http: { method: 'get', path: 'send' } }],
environment: {
COMPLEX_JSON_STRING: typed.stringify({
// typed.stringify will preserve all CloudFormation expressions (.ref, .arn, .getName) below:
queueUrl: typed.ref('MyQueue'),
queueArn: typed.arn('MyQueue'),
queueName: typed.getName('MyQueue'),
})
},
}),
}),
...
}
Use this function to be able to make a soft reference to a lambda. It means that instead of creating a CloudFormation reference like .arn('<ResourceId>')
, we build an ARN string for your resource, and we also validate if this resource was previously defined using .resources({'<ResourceId>': <ResourceBuilder> })
The main use case for this is to overcome a limitation in CloudFormation that does not allow a circular reference. It's very common issue when you have a lambda that references an IAM policy that references to the same lambda, creating a circular dependency.
ResourceId
should be a valid string literal type defined atIds
inTypedServerless.createDefault<Ids>()
type Ids = 'SendMessageFn';
const typed = TypedServerless.createDefault<Ids>();
const serverlessConfiguration: AWS = {
...
functions: typed.functions({
'SendMessageFn': ({ name }) => ({
name,
handler: './mylambda.handler',
events: [{ http: { method: 'get', path: 'send' } }],
environment: {
MY_LAMBDA_ARN: typed.buildLambdaArn('SendMessageFn'), // <-- It works!
// MY_LAMBDA_ARN: typed.arn('SendMessageFn'), // <-- It doesn't work: Circular reference error
},
}),
}),
...
}
TODO Document
TODO Document / example
TODO Document / example
Check out our examples.
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE
for more information.
- Inspired by cloudform
- Serverless Framework
- Serverless Typescript Types