Migrate Existing Fastify Project to Use TypeScript
Tags: fastify, beginners, typescript, webdev
Published: 2023-09-02 11:00:30

Several developers don't use TypeScript when they are starting a project or just trying to implement their new idea. TypeScript could be cumbersome and slow down the development process due to the need to add more code. However, when the project grows larger, it is inevitable that TypeScript can help a lot by catching bug and typing errors. This guide will show you how to migrate your already growing project in JavaScript to TypeScript. Specifically, this post will guide you how to migrate to TypeScript if you use Fastify as your framework.

---

## 1. Install Packages

Install these packages by adding `--save-dev` option
- `@types/node`
- 'typescript'
- `ts-node`
- 'fastify-tsconfig'
- 'concurrently'

```
npm i -D @types/node typescript ts-node fastify-tsconfig concurrently
```

If you want to use a testing library `tap`, also add `@types/tap` in `devDependencies`

```
npm i -D tap @types/tap
```

---

## 2. package.json

Change the `script` field of `package.json` into these lines
```json
"scripts": {
    "start": "npm run build:ts && fastify start -l info dist/app.js",
    "build:ts": "tsc",
    "watch:ts": "tsc -w",
    "dev": "npm run build:ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch:ts\" \"npm:dev:start\"",
    "dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P dist/app.js"
  },
```

If you want to enable testing using `tap`, add this line into `scripts`
```
"test": "npm run build:ts && tsc -p test/tsconfig.json && tap --ts \"test/**/*.test.ts\"",
```

---

## 3. tsconfig.json

Create a new `tsconfig.json` inside the root directory.
```json
{
  "extends": "fastify-tsconfig",
  "compilerOptions": {
    "outDir": "dist",
    "sourceMap": true
  },
  "include": ["src/**/*.ts"]
}
```

**Note**: Modify the `include` field to your project needs. In my case, all my app is stored inside a `/src` folder.

---

## 4. The App Itself

&nbsp;

> **Change all files that ends with `.js` to `.ts`.**

&nbsp;

### `app.js` or `server.js` or `index.js`

This file is the main entry point of your Node.js application

Before:
```js
module.exports = function (fastify, opts) { ... }
```
After:
```ts
export default function(fastify: FastifyInstance, opts: FastifyServerOptions) { ... }
```

&nbsp;

### Plugins

Change `require("module")` to `import module from "module"`, including `fastify-plugin`.

Before (`mongodb.js`):
```js
const fp = require('fastify-plugin')

module.exports = fp(async function (fastify, opts) {
  fastify.register(require('@fastify/mongodb'))
})
```

After (`mongodb.ts`):
```ts
import fp from 'fastify-plugin'
import fastifyMongodb from '@fastify/mongodb'

export default fp(async function (fastify, opts) {
  fastify.register(fastifyMongodb)
})
```

By using `import`, TypeScript knows that you will call `fastify.mongo` somewhere inside your app. **Note** that Fastify [stated](https://fastify.dev/docs/latest/Reference/TypeScript/#using-a-plugin) that "declaration merging is not very *smart*" i.e. if you `import` the plugin somewhere but did not call `fastify.register`, then TypeScript still assumes that you already registered it.

&nbsp;

Furthermore, some plugins have additional rules. Be sure to check their documentation. For example, if you use `@fastify/env` to load your `.config` file, you need to add this code.

```ts
declare module 'fastify' {
    interface FastifyInstance {
        config: {
            field1: string,
            field2: string
        };
    }
}

export default fp(async function (fastify, opts) {
    const schema = { ... }; // JSON-schema that match data
    const data = { field1: 'Value 1', field2: 'Value 2' };

    fastify.register(fastifyEnv, {
        schema: schema,
        data: data
    })
})
```

&nbsp;

### Custom Request

You might want to access a specific `request.body` or `request.query`. To tell TypeScript that a `request` has these fields, you need to create a new type that based on `FastifyRequest`. Example:

```ts
type CustomRequest = FastifyRequest<{
    Body: {
        username: string
    },
    Querystring: {
        product_id: string
    }
}>

function root(fastify: FastifyInstance, request: CustomRequest, reply: FastifyReply) {
    const username = request.body.username;
    const productId = request.query.product_id;
    // ...
}
```

---

## Further Readings

This guide mainly came from how `fastify-cli` generates a new Fastify project with TypeScript in mind. Check [this](https://github.com/fastify/fastify-cli#generate) out to read more.

If you have more issues, feel free to comment down below and I will try to help you. Here are other links that might help you.

- https://fastify.dev/docs/latest/Reference/TypeScript/#using-a-plugin