Production ready Typescript Serverless project configuration

tastefulelk

Sebastian Bille

Posted on March 16, 2021

Production ready Typescript Serverless project configuration

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:

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
Enter fullscreen mode Exit fullscreen mode

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!';
};
Enter fullscreen mode Exit fullscreen mode
// tests/myFunction.test.ts
import { handler } from '../src/myFunction';

describe('[myFunction]', () => {
  it('should not crash', async () => {
    await handler({}, {});
  });
});
Enter fullscreen mode Exit fullscreen mode

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. */
  }
}
Enter fullscreen mode Exit fullscreen mode

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',
};
Enter fullscreen mode Exit fullscreen mode

We can now finally run our tests with yarn jest to confirm that everything seems to work so far.

passing tests

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.

ESLint init wizard

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'],
};
Enter fullscreen mode Exit fullscreen mode

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 the env configuration
  • add 'plugin:import/typescript' to extends & import to plugins to get rid of module resolution errors
  • disable explicit file extension rule by adding 'import/extensions': 'off' to rules

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'),
          ],
        ],
      },
    ],
  },
};
Enter fullscreen mode Exit fullscreen mode

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 to true 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
Enter fullscreen mode Exit fullscreen mode

That's it! Hit yarn serverless deploy to deploy the project! We can verify that it works by running yarn serverless invoke -f myFunction

invoked function responded successfully

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

💖 💪 🙅 🚩
tastefulelk
Sebastian Bille

Posted on March 16, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related