DEV Community

Cover image for Optimizing Node API Development with SWC Compiler and ESLint
Francisco Mendes
Francisco Mendes

Posted on • Updated on

Optimizing Node API Development with SWC Compiler and ESLint

As our APIs get a larger code base, consequently, the time it takes to build and even hot reload will be longer. By the way, who ever made a small change and then had to wait almost three seconds for the API to hot reload? Or even making several changes in a short amount of time and then having problems with the process running?

This is where compilers like SWC help us, whether during the development of our applications or during the compilation and bundling process. In today's article we are going to setup an API in TypeScript and then we will proceed to configure the SWC together with ESLint.

During the development of the application, we will want the SWC to watch the changes we make to our TypeScript source code, as soon as it has any changes it will transpile to JavaScript from the same file we made the changes. Finally, we will use nodemon to watch the changes that occur in the transpiled code and we will hot reload the API as soon as there is a change.

When we need to put the API into production, just do the regular process, just run the build command and then we would have to run the start command.

enough talk gif

Project Setup

First let's start with the usual one, which is to create the project folder:

mkdir swc-config
cd swc-config
Enter fullscreen mode Exit fullscreen mode

Next, initialize a TypeScript project and add the necessary dependencies:

npm init -y
npm install -D typescript @types/node
Enter fullscreen mode Exit fullscreen mode

Next, create a tsconfig.json file and add the following configuration to it:

{
  "compilerOptions": {
    "target": "es2020",
    "module": "es2020",
    "allowJs": true,
    "removeComments": true,
    "resolveJsonModule": true,
    "typeRoots": [
      "./node_modules/@types"
    ],
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": [
      "es2020"
    ],
    "baseUrl": ".",
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "Node",
    "skipLibCheck": true,
    "paths": {
      "@routes/*": [
        "./src/routes/*"
      ],
      "@middlewares/*": [
        "./src/middlewares/*"
      ]
    }
  },
  "include": [
    "src/**/*"
  ],
  "exclude": ["node_modules"],
}
Enter fullscreen mode Exit fullscreen mode

As you may have noticed, we already defined some things in our tsconfig.json that I don't usually define in my articles, such as creating a path alias and using a "very current" version of ES.

With the configuration of our project in TypeScript, we can now install the necessary dependencies. In this project I will use the Koa framework, however this setup works with many others, such as Express, Fastify, etc.

# dependencies
npm install koa @koa/router koa-body

# dev dependencies
npm install -D @types/koa @types/koa__router
Enter fullscreen mode Exit fullscreen mode

Now with these base dependencies, we can create a simple api, starting with the entry file:

// @/src/main.ts
import Koa from 'koa'
import koaBody from 'koa-body'

import router from '@routes/index'

const startServer = async (): Promise<Koa> => {
  const app = new Koa()

  app.use(koaBody())
  app.use(router.routes())

  return app
}

startServer()
  .then((app) => app.listen(3333))
  .catch(console.error)
Enter fullscreen mode Exit fullscreen mode

Then we can create our routes:

// @/src/routes/index.ts
import KoaRouter from '@koa/router'
import { Context } from 'koa'

import { logger } from '@middlewares/index'

const router = new KoaRouter()

router.get('/', logger, (ctx: Context): void => {
  ctx.body = { message: 'Hello World' }
})

export default router
Enter fullscreen mode Exit fullscreen mode

And a simple middleware:

// @/src/routes/index.ts
import { Context, Next } from 'koa'

export const logger = async (ctx: Context, next: Next): Promise<Next> => {
  const start = Date.now()
  const ms = Date.now() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms} ms`)
  return await next()
}
Enter fullscreen mode Exit fullscreen mode

With this, we can now move on to the next step, which will be the SWC configuration.

SWC Setup

Now we can install the necessary dependencies to configure our SWC:

npm install -D @swc/cli @swc/core chokidar nodemon concurrently
Enter fullscreen mode Exit fullscreen mode

Next, let's create a .swcrc file and add the following configuration to it:

{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "tsx": false,
      "decorators": true,
      "dynamicImport": true
    },
    "target": "es2020",
    "paths": {
      "@routes/*": ["./src/routes/*"],
      "@middlewares/*": ["./src/middlewares/*"]
    },
    "baseUrl": "."
  },
  "module": {
    "type": "commonjs"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now let's add the necessary scripts to our package.json:

{
  // ...
  "scripts": {
    "dev": "concurrently \"npm run watch-compile\" \"npm run watch-dev\"",
    "watch-compile": "swc src -w --out-dir dist",
    "watch-dev": "nodemon --watch \"dist/**/*\" -e js ./dist/main.js",
    "build": "swc src -d dist",
    "start": "NODE_ENV=production node dist/main.js",
    "clean": "rm -rf dist"
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

In the watch-compile script swc will automatically transpile the code using chokidar. While the watch-dev script uses nodemon to hot reload the application. When the dev script is executed, concurrently executes both commands (watch-compile and watch-dev) at the same time so that swc transpiles the TypeScript code to JavaScript and nodemon hot reloads the API when notices a change.

With the SWC configured we can move on to the ESLint configuration.

ESLint Setup

First we will install ESLint as a development dependency:

npm install -D eslint
Enter fullscreen mode Exit fullscreen mode

Then we will initialize the eslint configuration by running the following command:

npx eslint --init
Enter fullscreen mode Exit fullscreen mode

Then in the terminal just make the following choices:

eslint setup through the terminal

Now we can go back to our package.json and add the following scripts:

{
  // ...
  "scripts": {
    // ...
    "lint": "eslint --ext .ts src",
    "lint:fix": "eslint --ext .ts src --fix"
  },
  // ...
}
Enter fullscreen mode Exit fullscreen mode

Finally, just create the .eslintignore file and add the following:

dist/
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope you enjoyed today's article and that it was useful to you, even if it is to try something new. Finally, I leave here the link of a repository in which I have a similar configuration, but using Express. See you 👋

Top comments (4)

Collapse
 
masekere profile image
Gift Masekere

What a great article

Collapse
 
vinibgoulart profile image
Vinicius Blazius Goulart

Awesome!!!

Collapse
 
tamusjroyce profile image
tamusjroyce

Kind of cool switching from ts-node typescript compiler to swc. Similar to how deno works.

I was hoping there was integration between eslint and swc. I have seen rules that take several minutes in eslint be seconds in swc/deno lint.

Thank you for the great article!

Collapse
 
kolynzb profile image
Atuhaire Collins

Thank you so much