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 ofapp.get()- same methods, different object. - Path changes: Routes use
"/"and"/:id"instead of"/users"and"/users/:id". - User-posts routes included: Notice that
/:id/postsand/:id/posts-with-userare 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()androuter.get()just like the users file. - Import interfaces: We import
PostWithUserfor 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
userRoutesandpostRoutesfor clarity. - Route connection:
app.use("/users", userRoutes)means all routes inuserRoutesget prefixed with/users. - Path combination: So
router.get("/", ...)becomesGET /users, androuter.get("/:id", ...)becomesGET /users/:id. - Same pattern: Posts routes work identically -
router.get("/", ...)becomesGET /posts.
The result
- Your main
index.tsbecomes 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.