Working with multiple Node.js applications in TypeScript often presents a unique challenge: how to efficiently and effectively share types across different parts of your project.
This is particularly crucial during a transition from JavaScript to TypeScript, where consistency in types is key to high-quality code and error prevention.
In this guide, we’ll dive into an elegant solution for this challenge using a monorepo structure and Yarn workspaces.
Initialize a New Repository
Start by creating a new repository to house your entire project. This will serve as the foundation for implementing a monorepo structure.
A monorepo is a version control repository that holds multiple projects, allowing you to manage them collectively.
Set up Yarn
Yarn is a fast and reliable dependency management tool that supports workspaces, making it an excellent choice for managing a monorepo. If you haven’t already, install Yarn by running:
npm install -g yarn
Code language: Bash (bash)
Then, initialize Yarn in your project:
yarn init -y
Code language: Bash (bash)
This will generate a package.json
file for your monorepo.
Create Package Structure
Let’s set up our project structure with a practical example. Imagine we are building a platform that includes a web application, an API, and a set of shared TypeScript types.
We will create directories for each package as follows:
packages/
api/
src/
package.json
...
web/
src/
package.json
...
shared-types/
src/
package.json
...
package.json
Code language: Bash (bash)
Each package directory should contain its own package.json
file, specifying its dependencies, scripts, and other relevant information.
Update Root Package.json
Open the package.json
file in the root of your project and modify it to include an array of packages and set private
to true
. This ensures that your monorepo packages are not accidentally published to the npm registry:
{
"name": "your-monorepo-name",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"start": "your-start-script",
"test": "your-test-script"
}
}
Code language: JSON / JSON with Comments (json)
Replace "your-monorepo-name"
, "your-start-script"
, and "your-test-script"
with your actual monorepo name and scripts.
Define Typescript Types
Inside the shared-types
package, define the TypeScript types you want to share. For example, create a file named types.ts
with the following content:
// packages/shared-types/src/index.ts
export interface User {
id: string;
username: string;
email: string;
}
Code language: TypeScript (typescript)
Build Shared-Types Package
Before linking the shared-types
package to other packages, build it to ensure TypeScript types are generated. Add a build script to the shared-types/package.json
file:
{
"name": "shared-types",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch "
},
"devDependencies": {
"typescript": "^5.3.3"
}
}
Code language: JSON / JSON with Comments (json)
Run the build script:
cd packages/shared-types
yarn build
This will compile the TypeScript files and generate the necessary declarations.
Alternatively, you can also run the build script in watch mode where it will auto-detect file changes and rebuild the package on the go:
cd packages/shared-types
yarn build:watch
Link Shared-Types Package to Web Package
Now, navigate to the web
package and link the shared-types
package:
cd packages/web
yarn add link:/../shared-types
Code language: Bash (bash)
This command establishes a symbolic link between the web
package and the shared-types
package, allowing the web application to access the shared types seamlessly.
Link Shared-Types Package to API Package
Repeat the linking process for the API package. Navigate to the api
package and run:
cd packages/api
yarn add link:/../shared-types
Code language: Bash (bash)
This links the api
package to the shared-types
package, enabling it to also leverage the shared TypeScript types.
Example: Using Shared Types in Web and API Packages
Now, let’s see how you can use the User
interface from the shared-types
package in both the web
and api
packages.
In the web
package, you can import the User
interface like this:
// packages/web/src/app.ts
import { User } from 'shared-types';
const user: User = {
id: '123',
username: 'john_doe',
email: 'john.doe@example.com',
};
Code language: TypeScript (typescript)
Similarly, in the api
package:
// packages/api/src/controllers/userController.ts
import { User } from 'shared-types';
const getUserById = (userId: string): User => {
// Logic to fetch user by userId
return {
id: userId,
username: 'retrieved_username',
email: 'retrieved_user@example.com',
};
};
Code language: TypeScript (typescript)
Conclusion
Adopting a monorepo structure and leveraging Yarn workspaces for your TypeScript-based Node.js applications brings numerous benefits.
It simplifies type sharing, fosters consistent code quality, and enhances overall project maintainability.
With this guide, you’re well-equipped to streamline your development process. Embark on this journey towards a more organized and efficient coding experience. Happy coding!