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.
Project Setup
First let's start with the usual one, which is to create the project folder:
mkdir swc-config
cd swc-config
Next, initialize a TypeScript project and add the necessary dependencies:
npm init -y
npm install -D typescript @types/node
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"],
}
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
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)
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
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()
}
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
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"
}
}
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"
},
// ...
}
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
Then we will initialize the eslint configuration by running the following command:
npx eslint --init
Then in the terminal just make the following choices:
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"
},
// ...
}
Finally, just create the .eslintignore
file and add the following:
dist/
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)
What a great article
Awesome!!!
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!
Thank you so much