Production ready Typescript Serverless project configuration
Sebastian Bille
Posted on March 16, 2021
Before we get started - if you're just here because you want a plug 'n' play starter, here's a project generator and here's an example repository! 🚀
Getting Typescript to run is usually pretty simple. You just follow a few lines of instructions from the first google link. Getting the project from that point to something that you can deploy with confidence is often surprisingly hard though. To get there, you'll at the very least want to have testing and linting set up. Tree shaking is another thing you'll probably want to be able to drastically reduce your bundle sizes.
This is going to be a walkthrough of my opinionated approach to achieving a simple but extensible setup that accomplishes these simple goals for a Serverless AWS project.
Project Initialization
Let's start off by creating a new project. Make sure you're in an empty directory and run npm init -y
. We're going to need a few tools to set this up:
- Serverless & Typescript
- ESLint for linting
- Jest for testing
- Webpack for bundling and optimizing
- ts-loader as a Typescript loader for webpack
- ts-jest as a Typescript preprocessor for jest
Let's add those as devDependencies right of the bat as well
yarn add eslint jest @types/jest serverless serverless-webpack ts-jest ts-loader typescript webpack -D
Application Code & Tests
We'll need some example Typescript code to run in our app. I prefer placing the application code in an src
folder and the test code in a tests
folder to keep them separate.
// src/myFunction.ts
export const handler = async (event: any, context: any) => {
return 'Hello from handler!';
};
// tests/myFunction.test.ts
import { handler } from '../src/myFunction';
describe('[myFunction]', () => {
it('should not crash', async () => {
await handler({}, {});
});
});
In order to transpile and run the Typescript code, we'll need a tsconfig.json
. We can generate one to start from by running yarn tsc --init
. Let's change both the target
and lib
to "ES2020"
as we want to be able to use modern ECMAScript features.
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": ["ES2020"], /* Specify library files to be included in the compilation. */
"strict": true, /* Enable all strict type-checking options. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
All we need now to be able to start developing and testing our app is to add a Jest configuration file. The default settings for Jest are pretty sensible to we don't have to care about most of those right now except for setting preset
to "ts-jest"
and the testEnvironment
to "node"
.
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
We can now finally run our tests with yarn jest
to confirm that everything seems to work so far.
Excellent, our tests are passing! 🎉
Linting
We'll be using ESLint for static code analysis and code style enforcement. ESLint configurations can quickly become a mess and it's by nature very subjective so let's keep it simple here. When we run yarn eslint --init
we'll get to answer a few questions about our preferences. In the image below I chose to create a small custom set of rules rather than using one of the "popular style guides" but it's completely up to you.
This is how mine ended up looking:
// .eslintrc.js
module.exports = {
env: {
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 12,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
rules: {
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
},
ignore: ['webpack.config.js'],
};
The AirBnb style guide has a few conflicts with our Typescript setup out of the box so if you choose to extend AirBnb style guide you'll have to do these steps as well:
- add
"jest"
to theenv
configuration - add
'plugin:import/typescript'
toextends
&import
toplugins
to get rid of module resolution errors - disable explicit file extension rule by adding
'import/extensions': 'off'
torules
Packaging & Deploying
Lastly, we need to configure Webpack and Serverless in order to build and deploy our app. For the Webpack configuration, we can use a pretty simple setup where we specify ts-loader
to be used as the loader for Typescript files.
// webpack.config.js
const path = require('path');
const slsw = require('serverless-webpack');
module.exports = {
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
entry: slsw.lib.entries,
resolve: {
extensions: ['.ts', 'tsx']
},
target: 'node',
module: {
rules: [
{
test: /\.(tsx?)$/,
loader: 'ts-loader',
exclude: [
[
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, '.serverless'),
path.resolve(__dirname, '.webpack'),
],
],
},
],
},
};
and finally, for the Serverless configuration we'll, in addition to some basic information about our app, add the following:
-
serverless-webpack
to the list of plugins - Set
package.individually
totrue
in order to bundle each function separately so that we can reduce the bundle size for each function by only including exactly what's needed to run it - As we've been using Yarn we should set the Webpack packager to
yarn
so that it'll respect our lock file.
// serverless.yml
service: sls-ts
plugins:
- serverless-webpack
package:
individually: true
custom:
webpack:
packager: yarn
provider:
name: aws
region: eu-north-1
runtime: nodejs14.x
functions:
myFunction:
handler: src/myFunction.handler
That's it! Hit yarn serverless deploy
to deploy the project! We can verify that it works by running yarn serverless invoke -f myFunction
Wrapping up
Project configuration is highly subjective and the complexity often grows as the projects grows but I hope this can serve as a starting point to build upon!
If you enjoyed this guide and want to see more, follow me on Twitter at @TastefulElk where I frequently write about serverless tech, AWS and developer productivity!
Happy hacking! 🚀
Resources
Posted on March 16, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.