Using TypeScript with Express.js

Express.js is a popular minimalist web application framework for Node.js, widely used for building web applications and APIs. TypeScript, on the other hand, is a typed superset of JavaScript that compiles to plain JavaScript. Combining TypeScript with Express.js offers several benefits, such as better code organization, enhanced maintainability, and early error detection through static type checking. In this blog post, we’ll explore how to use TypeScript with Express.js, covering fundamental concepts, usage methods, common practices, and best practices.

Table of Contents

  1. Fundamental Concepts
  2. Setting up a Project
  3. Basic Express.js Application in TypeScript
  4. Handling Routes and Middleware
  5. Error Handling
  6. Common Practices
  7. Best Practices
  8. Conclusion
  9. References

Fundamental Concepts

TypeScript

  • Types: TypeScript allows you to define types for variables, function parameters, and return values. This helps catch type-related errors at compile-time rather than runtime.
  • Interfaces: Interfaces are used to define the shape of an object. They can be used to enforce a certain structure for objects passed around in your application.
  • Classes: Classes in TypeScript are similar to classes in other object - oriented languages. They can have properties, methods, and constructors, and can be used to create objects with a specific behavior.

Express.js

  • Middleware: Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request - response cycle. They can be used for tasks such as logging, authentication, and error handling.
  • Routes: Routes are used to define the endpoints of your application. They specify the HTTP method (e.g., GET, POST, PUT, DELETE) and the URL path that the application should respond to.

Setting up a Project

  1. Initialize a new Node.js project:
    mkdir express-ts-app
    cd express-ts-app
    npm init -y
    
  2. Install dependencies:
    npm install express
    npm install --save-dev typescript @types/node @types/express ts-node-dev
    
    • express is the Express.js framework.
    • typescript is the TypeScript compiler.
    • @types/node and @types/express are type definition files for Node.js and Express.js respectively.
    • ts-node-dev is a tool that allows you to run TypeScript files directly without having to compile them first.
  3. Create a tsconfig.json file:
    npx tsc --init
    
    You can then modify the tsconfig.json file to suit your needs. A basic configuration might look like this:
    {
      "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true
      }
    }
    

Basic Express.js Application in TypeScript

  1. Create a src directory and an app.ts file inside it:
    mkdir src
    touch src/app.ts
    
  2. Write the basic Express.js application in TypeScript:
    import express from 'express';
    
    const app = express();
    const port = 3000;
    
    app.get('/', (req, res) => {
      res.send('Hello, World!');
    });
    
    app.listen(port, () => {
      console.log(`Server is running on port ${port}`);
    });
    
  3. Add a script to your package.json to start the application:
    {
      "scripts": {
        "start": "ts-node-dev src/app.ts"
      }
    }
    
  4. Run the application:
    npm start
    

Handling Routes and Middleware

Routes

You can create separate route files to organize your code better. For example, create a src/routes/userRoutes.ts file:

import express from 'express';

const router = express.Router();

router.get('/', (req, res) => {
  res.send('Get all users');
});

router.post('/', (req, res) => {
  res.send('Create a new user');
});

export default router;

Then, import and use the router in your app.ts file:

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

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

app.use(express.json());
app.use('/users', userRoutes);

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Middleware

Middleware functions can be used to perform tasks such as logging or authentication. Here is an example of a simple logging middleware:

import express from 'express';

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

const logger = (req, res, next) => {
  console.log(`Received ${req.method} request to ${req.url}`);
  next();
};

app.use(logger);

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

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Error Handling

Error handling in Express.js with TypeScript can be done using middleware functions. Here is an example of a basic error - handling middleware:

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

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

app.get('/error', (req, res, next) => {
  const error = new Error('Something went wrong');
  next(error);
});

const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err);
  res.status(500).send('Internal Server Error');
};

app.use(errorHandler);

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Common Practices

  • Separate Concerns: Keep your routes, controllers, models, and middleware in separate files or directories. This makes your code more modular and easier to maintain.
  • Use Interfaces for Request and Response Types: When handling requests and responses, use interfaces to define the shape of the data. For example:
import express, { Request, Response } from 'express';

interface User {
  name: string;
  age: number;
}

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

app.post('/users', (req: Request<{}, {}, User>, res: Response) => {
  const user: User = req.body;
  // Do something with the user
  res.send('User created');
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Best Practices

  • Leverage TypeScript’s Type System: Make full use of TypeScript’s type system to catch errors early. Use types for function parameters, return values, and variables.
  • Use Dependency Injection: When working with larger applications, consider using dependency injection to manage dependencies. This makes your code more testable and maintainable.
  • Follow a Coding Style Guide: Adopt a consistent coding style guide, such as the Airbnb JavaScript Style Guide, to keep your codebase clean and consistent.

Conclusion

Using TypeScript with Express.js offers numerous benefits, including improved code quality, better maintainability, and early error detection. By following the fundamental concepts, usage methods, common practices, and best practices outlined in this blog post, you can build robust and scalable web applications and APIs. Whether you are a beginner or an experienced developer, combining TypeScript and Express.js is a great choice for your next project.

References