Setting Up a Node.js, TypeScript, and Express Project

October 28, 2024 (3w ago)

Node.js is a popular runtime environment for building server-side applications. It allows you to write JavaScript code that runs on the server, making it easy to build scalable and performant applications. TypeScript, a superset of JavaScript, adds static typing to the language, enhancing error detection and code maintainability. Express, a minimal and flexible Node.js web application framework, offers a robust set of features for building web and mobile applications. In this tutorial, we'll walk through how to set up a Node.js, TypeScript, and Express project from scratch.

Why Node.js, TypeScript, and Express?

Before we jump into setting up our project, let's take a moment to understand why Node.js, TypeScript, and Express are a great combination for building web applications.

Prerequisites

To get started, make sure you have Node.js and npm (Node Package Manager) installed on your machine. You can verify this by running:

node -v
npm -v

If you don't have it installed, head over to the Node.js website and install it.

Get Coding

Initialize your project

mkdir node-typescript-express
cd node-typescript-express
npm init -y

Install essential packages

Now let's install a few npm packages that we'll need to build this project:

npm install express
npm install -D typescript @types/node @types/express ts-node nodemon

This installs Express and TypeScript essentials. We’ve used the -D flag for typescript, @types/node, @types/express, ts-node, and nodemon because these are developer dependencies, meaning they’re only needed during development.

Configuring typescript

Next, we'll configure typescript. Create a tsconfig.json file in the project's root folder by running:

npx tsc --init

Now, let’s adjust a few settings inside tsconfig.json:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

These options specify that we’ll compile to ES6, use CommonJS modules, and output our files into a dist folder.

Using a structure for our project

mkdir src

Writing the Server Code

Open src/index.ts and set up a simple Express server:

import express, { Request, Response } from 'express';
 
const app = express();
const PORT = process.env.PORT || 3000;
 
app.get('/', (req: Request, res: Response) => {
  res.send('Hello, TypeScript with Express!');
});
 
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Here, we import Express, define a basic route for the root path (/), and start the server on the specified port.

What’s Happening Here?

Configure Nodemon for Development

Instead of manually restarting the server each time we make a change, we can use Nodemon.

In package.json, update the scripts section:

"scripts": {
  "start": "node dist/index.js",
  "dev": "nodemon --exec ts-node src/index.ts"
}

Running our project

Let's test the sample endpoint we just created!

Run the following in your terminal while being in the project folder:

npm run dev

You should see a message in the terminal like: Server is running on http://localhost:3000. Open your browser and go to http://localhost:3000 to see "Hello, TypeScript with Express!" displayed.

Adding Routes and Controllers

Let's make our server more dynamic by adding more routes and controllers.

mkdir src/routes
import { Router, Request, Response } from 'express';
 
const router = Router();
 
router.get('/users', (req: Request, res: Response) => {
  res.send('List of users');
});
 
export default router;
import express, { Request, Response } from 'express';
import userRoutes from './routes/userRoutes';
 
const app = express();
const PORT = process.env.PORT || 3000;
 
app.use('/api', userRoutes);
 
app.get('/', (req: Request, res: Response) => {
  res.send('Hello, TypeScript with Express!');
});
 
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

What's happening here?

Adding middlewares

Middlewares are functions that have access to the request and response objects and can modify them. Let's add a simple logging middleware to log the request method and path.

mkdir src/middleware
import { Request, Response, NextFunction } from 'express';
 
const logger = (req: Request, res: Response, next: NextFunction) => {
  console.log(`${req.method} ${req.path}`);
  next();
};
 
export default logger;
import express, { Request, Response } from 'express';
import userRoutes from './routes/userRoutes';
import logger from './middleware/logger';
 
const app = express();
const PORT = process.env.PORT || 3000;
 
app.use(logger);
app.use('/api', userRoutes);
 
app.get('/', (req: Request, res: Response) => {
  res.send('Hello, TypeScript with Express!');
});
 
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

What's happening here?

Conclusion

This setup gives you a structured, scalable project with Node.js, TypeScript, and Express. Here’s a quick recap of what we covered:

Now you’re ready to expand on this setup, whether that means adding database connectivity, more detailed routes, or additional middleware for error handling. Enjoy building with this efficient and type-safe setup!