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.
Node.js: Node.js is fast, lightweight, and scalable, making it a great choice for building server-side applications.
Typescript: Adds type safety to JavaScript, making your code more predictable and easier to debug.
Express: A minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile 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
- Create your project folder and enter into it by running:
mkdir node-typescript-express
cd node-typescript-express
- Next, initialize Node.js to add a
package.json
file by running:
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
- Create a
src
folder in the root directory to hold your TypeScript files:
mkdir src
- Inside
src
, create anindex.ts
file. This will be our entry point.
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?
-
app.get
defines a GET route for our root URL. -
req
andres
are typed automatically using TypeScript, making it clear what kind of objects we’re working with. - The server runs on
http://localhost:3000
(or another port if specified in the environment).
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"
}
-
start
runs the compiled Javascript in production mode. -
dev
runs the server in development mode withts-node
automatically restarting whenever any changes are made.
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.
- Create a
routes
folder insidesrc
.
mkdir src/routes
- Create a new file in
src/routes
calleduserRoutes.ts
. This will define routes related to user actions.
import { Router, Request, Response } from 'express';
const router = Router();
router.get('/users', (req: Request, res: Response) => {
res.send('List of users');
});
export default router;
- Modify
index.ts
to use this new route:
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?
- In the
userRoutes.ts
file, we're creating a new router usingexpress.Router()
and defining a route for/users
. - In
index.ts
, we're importing theuserRoutes
and using it with the/api
prefix. - This means that the
/users
route will be available athttp://localhost:3000/api/users
.
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.
- Create a
middleware
folder.
mkdir src/middleware
- Create a new file in
src/middleware
calledlogger.ts
.
import { Request, Response, NextFunction } from 'express';
const logger = (req: Request, res: Response, next: NextFunction) => {
console.log(`${req.method} ${req.path}`);
next();
};
export default logger;
- Update
index.ts
to use this middleware:
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?
- In the
logger.ts
file, we're defining a middleware function that logs the request method and path. - In
index.ts
, we're importing thelogger
middleware and using it withapp.use()
to apply it to all routes. -
app.use()
is used to mount the middleware function on the specified path.
Conclusion
This setup gives you a structured, scalable project with Node.js, TypeScript, and Express. Here’s a quick recap of what we covered:
- Project setup and dependency installation.
- TypeScript configuration.
- A basic Express server setup.
- Organized routes and middleware.
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!
Top comments (1)
No need for nodemon here. You can just use
ts-node-dev
instead ofts-node
, or even better, you could even serve ts files directly with node by providing a loadernode --loader ts-node/esm