Making your code neater with TypeScript Paths

Making your code neater with TypeScript Paths

When I started doing development with TypeScript one of the first things that irked me was all those relative paths in import statements.

Code organisation and creating layers for maintainability is one of the things I'm most OCD about, so things like a service to call Prismics API end up in a folder called services. However, to then use it in a NextJs page a few folders deep ends up with an import statement such as this.

1import { prismicService } from '../../../src/services/Prismic/Prismic.service'

Yuck. Look at all those ../../ and to make things worse this also means if you move a file its no longer pointing in the right place.

Fortunately, there is a solution. Typescript supports path mapping through the tsconfig.json file.

First set a base URL property in the compiler options.

2 "compilerOptions": {
3 "baseUrl": ".",
4 }

At this point, you could get rid of all those ../ and just start at the src. e.g.

1import { prismicService } from 'src/services/Prismic/Prismic.service'

This is a lot better and we can now move files around, but it's still not perfect. The fact it's in a folder called src is a bit irrelevant for my liking.

Using the path's property we can define a grouping even lower down. e.g.

1// tsconfig.json
3 "compilerOptions": {
4 "baseUrl": ".",
5 "paths": {
6 "@Services/*": ["src/services/*"]
7 }
8 }
11// New import
12import { prismicService } from '@Services/Prismic/Prismic.service'

For more information on TypeScript paths check out the official documentation here.

Going a bit further

As well as just making your code a bit easier to read. This also makes a simple solution to swapping out code in a way that would otherwise need a dependency injection framework.

Coming from a .net background where dependency injection and interfaces are frequently used there can be a desire to do it with TypeScript too. Although it is possible (you can read my blog post on dependency injection with NextJs and Tsyringe here) it has quite a heavy overhead to the amount of code you need to write.

A simpler solution can be like what Vercel have done in NextJs commerce for the different providers.

Check out this part of their tsconfig file.

1 "paths": {
2 "@lib/*": ["lib/*"],
3 "@utils/*": ["utils/*"],
4 "@config/*": ["config/*"],
5 "@assets/*": ["assets/*"],
6 "@components/*": ["components/*"],
7 "@commerce": ["../packages/commerce/src"],
8 "@commerce/*": ["../packages/commerce/src/*"],
9 "@framework": ["../packages/local/src"],
10 "@framework/*": ["../packages/local/src/*"]
11 }

That @framework section at the end defaults to local, but gets swapped out to a commerce provider like bigcommerce or shopify. As each one implements the same methods, anything importing from @framework simply uses the correct code when the solution is built.