When building APIs with Node.js and TypeScript, Express is often the framework of choice for handling routes and middleware. To interact with MongoDB, we use Mongoose, a popular ODM (Object Data Modeling) library that provides a schema-based solution to model our data.
In this post, you’ll learn how to set up a simple Express + TypeScript project using Mongoose to perform CRUD operations — Create, Read, Update, and Delete — on a single resource.
We’ll build a simple API to manage a collection of Books, where each book has:
title
author
year
isPublished
What is Mongoose?
Mongoose simplifies working with MongoDB by providing:
Schema validation
Query helpers and middleware
Model methods
TypeScript type support
Essentially, it helps you structure your MongoDB collections more predictably and safely.
Prerequisites
Node.js (v16 or higher)
Basic knowledge of Express and TypeScript
MongoDB running locally or a MongoDB Atlas account
Code editor (e.g., VS Code)
Step 1: Set Up the Project
- Initialize the project:
Create a new directory and set up a Node.js project:
mkdir express-mongoose-ts
cd express-mongoose-ts
npm init -y
- Install dependencies:
Install Express, TypeScript, and necessary type definitions:
npm install express mongoose
npm install -D typescript @types/node @types/express ts-node nodemon
- Set up TypeScript:
Initialize TypeScript configuration:
npx tsc --init
Update tsconfig.json
to include:
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"rootDir": "./src",
"outDir": "./dist",
"esModuleInterop": true,
"strict": true
}
}
- Configure scripts:
Update package.json
with:
"scripts": {
"start": "node dist/server.js",
"build": "tsc p .",
"dev": "nodemon src/server.ts"
}
Project Structure
Here’s how your folder should look:
express-mongoose-ts/
├── src/
│ ├── config/
│ │ └── db.ts
│ ├── models/
│ │ └── book.model.ts
│ ├── routes/
│ │ └── book.route.ts
│ ├── controllers/
│ │ └── book.controller.ts
│ └── server.ts
├── package.json
├── tsconfig.json
Connecting to MongoDB
Insidesrc/config/db.ts
:
import mongoose from "mongoose";
export async function connectDB(): Promise<void> {
try {
const conn = await mongoose.connect("mongodb://localhost:27017/express_mongoose_ts");
console.log(`MongoDB connected: ${conn.connection.host}`);
} catch (err) {
console.error("Database connection failed:", err);
process.exit(1);
}
}
Defining the Mongoose Model
Create src/models/book.model.ts
:
import { Schema, model } from "mongoose";
const bookSchema = new Schema(
{
title: { type: String, required: true },
author: { type: String, required: true },
year: { type: Number, required: true },
isPublished: { type: Boolean, default: false },
},
{ timestamps: true }
);
export const Book = model("Book", bookSchema);
Here, we:
Create a Mongoose schema
Export a model called Book
Writing the Controller Functions
Create src/controllers/book.controller.ts
:
import { Request, Response } from "express";
import { Book } from "../models/book.model";
// Create a new book
export async function createBook(req: Request, res: Response) {
try {
const book = await Book.create(req.body);
res.status(201).json(book);
} catch (error) {
res.status(400).json({ message: "Error creating book", error });
}
}
// Get all books
export async function getBooks(req: Request, res: Response) {
try {
const books = await Book.find();
res.status(200).json(books);
} catch (error) {
res.status(500).json({ message: "Error fetching books", error });
}
}
// Get a single book by ID
export async function getBookById(req: Request, res: Response) {
try {
const book = await Book.findById(req.params.id);
if (!book) return res.status(404).json({ message: "Book not found" });
res.status(200).json(book);
} catch (error) {
res.status(500).json({ message: "Error fetching book", error });
}
}
// Update a book
export async function updateBook(req: Request, res: Response) {
try {
const book = await Book.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
if (!book) return res.status(404).json({ message: "Book not found" });
res.status(200).json(book);
} catch (error) {
res.status(500).json({ message: "Error updating book", error });
}
}
// Delete a book
export async function deleteBook(req: Request, res: Response) {
try {
const book = await Book.findByIdAndDelete(req.params.id);
if (!book) return res.status(404).json({ message: "Book not found" });
res.status(200).json({ message: "Book deleted successfully" });
} catch (error) {
res.status(500).json({ message: "Error deleting book", error });
}
}
Creating the Express Routes
Create src/routes/book.route.ts
:
import { Router } from "express";
import {
createBook,
getBooks,
getBookById,
updateBook,
deleteBook,
} from "../controllers/book.controller";
const router = Router();
router.post("/", createBook);
router.get("/", getBooks);
router.get("/:id", getBookById);
router.put("/:id", updateBook);
router.delete("/:id", deleteBook);
export default router;
Setting Up the App Entry Point
Create src/server.ts
:
import express, { Application } from "express";
import { connectDB } from "./config/db";
import bookRouter from "./routes/book.route";
const app: Application = express();
// Enable URL-encoded form data parsing
app.use(express.urlencoded({ extended: true }));
// Middleware to parse JSON
app.use(express.json());
// Routes
app.use("/api/books", bookRouter);
// Start Server
const PORT = process.env.PORT || 4000;
app.listen(PORT, async () => {
await connectDB();
console.log(`Server running on http://localhost:${PORT}`);
});
Testing the API
Start the server:
npm run dev
If you see:
MongoDB connected successfully
Server running on http://localhost:4000
You’re ready to test using Postman or cURL.
Examples:
- POST
/api/books
{
"title": "The Alchemist",
"author": "Paulo Coelho",
"year": 1988,
"isPublished": true
}
GET
/api/books
→ Returns all booksGET
/api/books/:id
→ Returns one bookPUT
/api/books/:id
→ Updates a bookDELETE
/api/books/:id
→ Deletes a book
Conclusion
You’ve just built a simple CRUD API using Express + Mongoose + TypeScript.
In a real-world project, you’d add features like:
Input validation with
express-validator
orzod
Environment variables using
dotenv
Better error handling and logging
But this setup is a strong foundation for learning backend development with TypeScript and MongoDB.
The code for this blog is here.
Top comments (0)