How to Set up RESTful API Routing using Express.js with TypeScript

Note: This post is part of Learn How to Use TypeScript With Node.js and Express.js series. Click here to see the first post of the series.

Create API Endpoint Routes

So far our server has only one API endpoint / which we defined in the index.ts returning a Hello World! message.

app.get('/', (req: Request, res: Response) => {
  res.send('Hello World!');
});

Let’s add more routes to our API. Before we move forward, it is important to mention that paths are generally defined in a different file rather than in the index.ts file as our API can grow in a matter of no time.

Therefore, we are going to create a folder called api inside the src folder. Also, we are going to create a routes.ts file inside the api folder.

Current folder structure

Since I am a big fan of soccer, I’m going to create a set of RESTful API endpoints related to soccer in the routes.ts file. However, we cannot accomplish this unless we assign the endpoints to an express application, which we created in the index.ts file. Therefore, we need to generate routes inside the routes.ts file and import them to the index.ts file.

Let’s set up API endpoints to perform CRUD operations for soccer teams. For the time being, we are going to import the Router from Express and start with a GET endpoint.


import { Router } from 'express';

const router = Router();
router
  .route('/teams')
  .get((req, res) => {
    // add logic here
  });

If you are using a smart IDE such as VS Code, you will notice you can get access to intellisense which allows you to see what all req and res Express objects have available.

At this point, we didn’t have the need to define the types for the req and res Express.js objects. However, that would defeat the purpose of using TypeScript rather than JavaScript for our Express.js app. At the same time, you will see “underlined” errors generated by the IDE (especially if you are using VS Code), as “the parameters implicitly have an ‘any’ type'”.

That’s why, we are going to import Request and Response types and define the types for req and res parameters.

import { Request, Response, Router } from 'express';

const router = Router();
router
  .route('/teams')
  .get((req: Request, res: Response) => {
    // add logic here
  });

Time to add logic to our API endpoints. For now, we are going to keep it simple and avoid fetching and/or saving data to a database. We are going to store an array of soccer teams and perform simple CRUD operations.

const TEAMS = [
  { id: 1, name: 'Real Madrid', league: 'La Liga' },
  { id: 2, name: 'Barcelona', league: 'La Liga' },
  { id: 3, name: 'Manchester United', league: 'Premier League' },
  { id: 4, name: 'Liverpool', league: 'Premier League' },
  { id: 5, name: 'Arsenal', league: 'Premier League' },
  { id: 6, name: 'Inter', league: 'Serie A' },
  { id: 7, name: 'Milan', league: 'Serie A' },
  { id: 8, name: 'Juventus', league: 'Serie A' },
];

For our GET request, it will be enough for us to return the array of teams as the response.

router
  .route('/teams')
  .get((req: Request, res: Response) => {
    res.send(TEAMS);
  });

Finally, export the router as it will be needed in the express application.

export default router;

Go back to the index.ts file and import the routes. Once you do so, mount the routes in the application. Go ahead and remove the endpoint we initially set. Your index.ts file should look like the following:

import express from 'express';
import routes from './api/routes';

const app = express();
const port = 3000;

app.use('/api/', routes);

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
});

Open your browser and make sure http://localhost:3000/api/teams is receiving the soccer teams.

Refactoring the Code Logic

Although there’s no right or wrong answer, it is common in the industry to leave routes.ts file dedicated for only defining routes. In our case, we are doing two things, setting up the routes while also adding the logic. That’s why we are going to do some refactoring.

First, generate a new folder called teams inside the api folder. Inside the teams folder we are going to create two files: teams.controller.ts. There, we are going to store the logic that needs to be executed for our API endpoints. Since we only have one API endpoint, we are going to create a variable called getTeams and assign a function we had assigned to return the list of teams.

Therefore, your teams.controller.ts file should look like this:

import { Request, Response } from 'express';

const TEAMS = [
  { id: 1, name: 'Real Madrid', league: 'La Liga' },
  { id: 2, name: 'Barcelona', league: 'La Liga' },
  { id: 3, name: 'Manchester United', league: 'Premier League' },
  { id: 4, name: 'Liverpool', league: 'Premier League' },
  { id: 5, name: 'Arsenal', league: 'Premier League' },
  { id: 6, name: 'Inter', league: 'Serie A' },
  { id: 7, name: 'Milan', league: 'Serie A' },
  { id: 8, name: 'Juventus', league: 'Serie A' },
];

export const getTeams = (req: Request, res: Response) => {
  res.send(TEAMS);
};

Go back to the routes.ts file and update the code by importing the getTeams variable and using same variable as the handler of the GET /teams request.

import { Router } from 'express';
import { getTeams } from './teams/teams.controller'

const router = Router();

router.route('/teams').get(getTeams);

export default router;

Reorganizing the Project Structure Even More

Depending on the project you are working on, your API might be small enough that it won’t be that necessary to make further changes in the file organization of the project. However, whenever you start having more than 5 API unrelated endpoints, such as, /teams , /players, /stadiums in the case of our soccer API, it will be a good idea to store the routes independently, and then combine them all inside the routes.ts file.

Therefore, we are going to start by creating a file called teams.routes.ts inside the teams folder and store the routes associated to information related to the soccer teams. Once it is created, add the routes.

import { Router } from 'express';
import { getTeams } from './teams.controller';

const router = Router();

router.route('/').get(getTeams);

export default router;

Then, go back to the routes.ts file and update it by importing all the teams routes with a base path of /teams.

import { Router } from 'express';
import TeamsRoutes from './teams/teams.routes';

const router = Router();

router.use('/teams', TeamsRoutes);

export default router;

Look at how better organized this will be in the case of having several API endpoints.

router.use('/teams', TeamsRoutes);
router.use('/players', PlayersRoutes);
router.use('/stadiums', StadiumsRoutes);
router.use('/leagues', LeaguesRoutes);

After refactoring and reorganizing your project, go ahead and verify one more time you are getting team records from our endpoint http://localhost:3000/api/teams.

Do You Want to Keep Learning?

That was all for this article. However, if you are keen to learn more and keep working on setting up a Node.js + Express.js project using Typescript, feel free to check out the next article: How to Write Node.js REST Middleware with Express.js with TypeScript.