Adding TypeScript Type Definitions


When our project was set up, we added a bunch of type definitions as dependencies in package.json:

  "devDependencies": {
    "@types/redis": "^2.8.13",
    "@types/jest": "^23.3.14",
    "@types/koa": "^2.0.49",
    "@types/koa-bodyparser": "^4.3.0",
    "@types/koa-logger": "^3.1.1",
    "@types/koa-router": "^7.0.42",
    "@types/koa2-cors": "^2.0.1",
    "@types/node": "^10.14.12",
    "@types/supertest": "^2.0.8",
    "husky": "^1.3.1",
    "jest": "^24.8.0",
    "prettier": "1.15.1",
    "pretty-quick": "^1.11.1",
    "supertest": "^4.0.2",
    "ts-jest": "^23.10.5",
    "ts-node-dev": "^1.0.0-pre.40"
  },

All of those @type/* packages are scoped packages, and provide TypeScript with type definitions to help it understand what things are in our code.

In short, it would be really painful if we had to add type definitions for all the third party code we import / require in our projects.

Doing so would also be a highly duplicated effort. If I use Koa, and you use Koa, and several tens of thousands of other people also use Koa, and we each had to go through and define interfaces, and types, and overloads, and everything else that TypeScript gives us, collectively we would have accumulated a lot of wasted effort.

Instead, TypeScript leverages the amazing work done by a whole host of type definition maintainers and contributors from the Definitely Typed project.

If we remove these definitions, we would see a whole host of TypeScript compilation errors:

[ERROR] 14:27:32 ⨯ Unable to compile TypeScript
src/server.ts (1,17): Could not find a declaration file for module 'koa'. '/Users/codereview/Development/koa-ts-tutorial/node_modules/koa/lib/application.js' implicitly has an 'any' type.
  Try `npm install @types/koa` if it exists or add a new declaration (.d.ts) file containing `declare module 'koa';` (7016)
src/server.ts (2,24): Could not find a declaration file for module 'koa-bodyparser'. '/Users/codereview/Development/koa-ts-tutorial/node_modules/koa-bodyparser/index.js' implicitly has an 'any' type.
  Try `npm install @types/koa-bodyparser` if it exists or add a new declaration (.d.ts) file containing `declare module 'koa-bodyparser';` (7016)
src/server.ts (3,18): Could not find a declaration file for module 'koa2-cors'. '/Users/codereview/Development/koa-ts-tutorial/node_modules/koa2-cors/dist/index.js' implicitly has an 'any' type.
  Try `npm install @types/koa2-cors` if it exists or add a new declaration (.d.ts) file containing `declare module 'koa2-cors';` (7016)
src/server.ts (4,20): Could not find a declaration file for module 'koa-logger'. '/Users/codereview/Development/koa-ts-tutorial/node_modules/koa-logger/index.js' implicitly has an 'any' type.
  Try `npm install @types/koa-logger` if it exists or add a new declaration (.d.ts) file containing `declare module 'koa-logger';` (7016)
src/server.ts (26,16): Parameter 'err' implicitly has an 'any' type. (7006)

Without the additional type definitions, we would have to go through and definite what each of the various function parameters are. Or we could cheat, and allow TypeScript to compile with noImplicitAny: false.

Adding Our Own Type Definitions

It's a really nice thing that pretty much every popular third party library out there has at least some existing type definitions.

There are circumstances where new additions to libraries do not have type definitions yet, and that is a different challenge. But by and large, third party stuff is covered either immediately, or soon after changes are made.

We need to help TypeScript better understand our own code.

As a gentle introduction, we're going to define the interface of our Config object.

This will flag a small issue that we'd otherwise likely be unaware of.

interface IConfig {
  port: number;
}

export const config: IConfig = {
  port: parseInt(process.env.PORT) || 7654,
};

At first this seems legit.

Yet the compiler complains:

[ERROR] 10:48:27 ⨯ Unable to compile TypeScript
src/config.ts (6,18): Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'. (2345)

The problem here is assumption.

I've assumed - wrongly - that the port value should be a number. All environment variables seemingly should be a string.

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fd7bea617cc47ffd252bf90a477fcf6a6b6c3ba5/types/node/index.d.ts#L412

    export interface ProcessEnv {
        [key: string]: string | undefined;
    }

That's a fairly common syntax in TypeScript called an index signature.

In short, process.env.* should be a key / value pair, where the key can be any string, and the value can either be a string, or undefined.

We are setting our interface, and implementation to expect a number. A number won't be cast to a string. We could either cast it, or just change the implementation to use a string instead:

interface IConfig {
  port: string;
}

export const config: IConfig = {
  port: process.env.PORT || '7654',
};

The server should restart for you at this point.

Episodes

# Title Duration
1 Project Setup 04:01
2 Configuring TypeScript 02:07
3 Adding Our First Endpoint 05:36
4 Extracting Routes and Config 02:49
5 Adding TypeScript Type Definitions 04:22
6 Testing with Jest 03:16