Express Router lets you group related routes together in separate files. Instead of defining all routes in a single file, you create separate routers for different resources, such as users or posts.

Moving User Routes to a Separate File

Move all routes whose paths begin with /users from your index.ts to a dedicated file. Notice how /users becomes / and use router.get() instead of app.get().

Create routes/users.ts:

import { Router } from "express";
import { ResultSetHeader } from "mysql2";
import { pool } from "../database";
import { User, Post, PostWithUser } from "../interfaces";

const router = Router();

router.get("/", async (req, res) => {
  // Move your existing GET /users logic here
});

router.get("/:id", async (req, res) => {
  // Move your existing GET /users/:id logic here
});

router.get("/:id/posts", async (req, res) => {
  // Move your existing GET /users/:id/posts logic here
});

router.get("/:id/posts-with-user", async (req, res) => {
  // Move your existing JOIN query logic here
});

router.post("/", async (req, res) => {
  // Move your existing POST /users logic here
});

router.put("/:id", async (req, res) => {
  // Move your existing PUT /users/:id logic here
});

router.patch("/:id", async (req, res) => {
  // Move your existing PATCH /users/:id logic here
});

router.delete("/:id", async (req, res) => {
  // Move your existing DELETE /users/:id logic here
});

export default router;

What’s happening here

  • Import Router: We import { Router } from Express, which is the function that creates routers.
  • Import ResultSetHeader: We import this from mysql2 for typing POST request results (no longer needed in index.ts).
  • Import interfaces: We import { User, Post, PostWithUser } from our interfaces file for type safety.
  • Create router instance: const router = Router() creates a new router object.
  • Router methods: We use router.get() instead of app.get() - same methods, different object.
  • Path changes: Routes use "/" and "/:id" instead of "/users" and "/users/:id".
  • User-posts routes included: Notice that /:id/posts and /:id/posts-with-user are here because they’re accessed via endpoints beginning with /users/. Although these routes return posts data, they belong in the user’s router based on their URL structure.
  • Most routes are here: The user’s router contains most of our endpoints because Module 2 focused heavily on user-related functionality.
  • Same code: The actual endpoint code is identical to what you had in index.ts.
  • Export: We export the router so it can be used in the main file.

All the endpoint URLs remain the same. For example, GET /users/:id still works as GET /users/:id - we’ll see soon how the routes get imported and connected in index.ts.

The only route remaining to move is the one that just returns posts (GET /posts), and we’ll move that into a posts router now.

Moving Posts Routes to a Separate File

Create routes/posts.ts:

Move the app.get("/posts", ... endpoint into this file.

import { Router } from "express";
import { pool } from "../database";
import { PostWithUser } from "../interfaces";

const router = Router();

router.get("/", async (req, res) => {
  // Move your existing GET /posts logic here
});

export default router;

What’s happening here

  • Same pattern: Uses Router() and router.get() just like the users file.
  • Import interfaces: We import PostWithUser for our posts endpoints.
  • Posts endpoints: Contains the main posts route from Module 2, Lesson 4.
  • Complete CRUD: Although we didn’t cover it in the lessons, in a real application, you’d also have POST, PUT, PATCH, and DELETE endpoints for posts in this file.
  • Focused responsibility: Only handles direct post endpoints.

Updating Your Main Application

Now update your index.ts to use these router files:

import express from "express";
import userRoutes from "./routes/users";
import postRoutes from "./routes/posts";

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

// Connect the route modules
app.use("/users", userRoutes);
app.use("/posts", postRoutes);

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

How this works

  • Default exports: Both route files use export default router, so we can import them with any name.
  • Import aliases: We choose to call them userRoutes and postRoutes for clarity.
  • Route connection: app.use("/users", userRoutes) means all routes in userRoutes get prefixed with /users.
  • Path combination: So router.get("/", ...) becomes GET /users, and router.get("/:id", ...) becomes GET /users/:id.
  • Same pattern: Posts routes work identically - router.get("/", ...) becomes GET /posts.

The result

  • Your main index.ts becomes much smaller and more focused.
  • User routes are isolated in their own file.
  • Posts routes are isolated in their own file.
  • Each file has a single, clear responsibility.


Repo link

Tags: