Skip to main content

setting-up-expressjs

Setting up and Using TypeScript with Express.js:

Let's break down each of these steps with corresponding TypeScript code examples where applicable:

  • Initial setup:

    Install the necessary packages including TypeScript, Node.js, Express, and their type definitions.

    • Example commands:
      npm install typescript @types/node express @types/express
  • tsconfig.json:

    Create a tsconfig.json file tailored for Node.js.

    • Example tsconfig.json content:
      {
      "compilerOptions": {
      "module": "commonjs",
      "esModuleInterop": true,
      "target": "es2018",
      "moduleResolution": "node",
      "outDir": "./dist",
      "strict": true
      },
      "include": ["src/**/*"],
      "exclude": ["node_modules"]
      }
  • TypeScript Node:

    Use ts-node in development to execute TypeScript files.

    • Example command:
      npx ts-node src/index.ts
  • Middleware types:

    Type your middleware using types from @types/express.

    • Example middleware:

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

      const myMiddleware = (req: Request, res: Response, next: NextFunction) => {
      // Middleware logic
      next();
      };
  • Route handlers:

    Ensure your route handlers are properly typed.

    • Example route handler:
      app.get('/user/:id', (req: Request, res: Response) => {
      const userId = req.params.id;
      // Fetch user logic
      res.json(user);
      });
  • Error handling:

    Create custom error handlers following the ErrorRequestHandler type.

    • Example error handler:

      import { ErrorRequestHandler } from 'express';

      const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
      // Error handling logic
      res.status(500).send('An error occurred');
      };
  • Interface extension:

    Extend the Express Request and Response interfaces when needed.

    • Example interface extension:

      import { Request, Response } from 'express';

      interface RequestWithUser extends Request {
      user?: User; // Custom property added to the request type
      }

      const getUserMiddleware = (req: RequestWithUser, res: Response, next: NextFunction) => {
      // Middleware logic to add user to request
      req.user = { id: 1, name: 'John Doe' };
      next();
      };

These steps and examples guide you through setting up a Node.js server using Express with TypeScript, ensuring type safety and taking advantage of TypeScript's features for a better development experience.

For each of these points, I'll explain how they can be applied in a TypeScript project, particularly for a Node.js and Express server:

  • Organizing routes:

    Break down your server's routes into separate modules.

    • Example:

      // In userRoutes.ts
      import { Router } from 'express';
      const router = Router();

      router.get('/users', /* ... */);
      router.post('/users', /* ... */);

      export default router;

      // In your main server file, e.g., app.ts
      import userRoutes from './userRoutes';

      app.use('/api', userRoutes);
  • Async functions:

    Wrap async route handlers to handle rejections.

    • Example wrapper:

      const asyncHandler = (fn) => (req, res, next) =>
      Promise.resolve(fn(req, res, next)).catch(next);

      // In your route
      app.get('/users', asyncHandler(async (req, res) => {
      // your async logic here
      }));
  • Request validation:

    Use runtime validation libraries alongside TypeScript types.

    • Example with joi:

      import Joi from 'joi';

      const userSchema = Joi.object({
      username: Joi.string().alphanum().min(3).max(30).required(),
      // other validations
      });

      app.post('/users', (req, res, next) => {
      const { error } = userSchema.validate(req.body);
      if (error) return res.status(400).send(error.details);
      // proceed with validated data
      });
  • Environmental typing:

    Type your environment variables for better safety.

    • Example:

      interface Env {
      NODE_ENV: 'development' | 'production';
      PORT: string;
      // other env variables
      }

      const env: Env = process.env as any;
  • Database interaction:

    Utilize ORMs that have TypeScript support.

    • Example with TypeORM:

      import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

      @Entity()
      class User {
      @PrimaryGeneratedColumn()
      id: number;

      @Column()
      name: string;

      // other columns
      }
  • Project structure:

    Maintain a clear and organized project structure.

    • Suggested folder structure:
      src/
      controllers/
      entities/
      middlewares/
      routes/
      services/
      utils/
      app.ts
      server.ts
  • Testing:

    Write tests using TypeScript.

    • Example with Jest:
      // In user.test.ts
      describe('User routes', () => {
      it('should create a new user', async () => {
      // your test logic here, with types
      });
      });
  • Building for production:

    Set up scripts to compile and run your TypeScript app.

    • Example package.json scripts:
      {
      "scripts": {
      "build": "tsc",
      "start": "node dist/app.js",
      "dev": "ts-node src/app.ts"
      }
      }
      Make sure to test the JavaScript output in a production-like environment to catch any issues before deployment.

By following these practices, you can leverage TypeScript's capabilities to create a more maintainable, type-safe backend environment using Node.js and Express.