Lesson 5: Building the Backend with Express and MongoDB
Goal
Set up a backend server using Express and MongoDB to handle API endpoints for the TodoList app. Write unit and integration tests using Jest, Supertest, and MongoMemoryServer to ensure the server works correctly.
1. Learning Objectives
By the end of this lesson, students will:
- Set up an Express server to serve API endpoints.
- Connect the server to MongoDB using Mongoose.
- Create routes for CRUD operations (Create, Read, Update, Delete).
- Integrate a local MongoDB database for testing.
- Write tests for the backend using Jest, Supertest, and MongoMemoryServer.
2. Setting Up the Backend
2.1. Initialize the Project
- Create a new folder for the backend:
mkdir backend
cd backend
npm init -y- Install Dependencies:
npm install express mongoose dotenv cors
npm install --save-dev jest ts-node supertest @types/jest @types/express @types/node mongodb-memory-server2.2. Folder Structure
Organize your backend project like this:
backend/
│
├── src/
│ ├── config/ # MongoDB connection
│ ├── controllers/ # Logic for routes
│ ├── models/ # Mongoose schemas
│ ├── routes/ # API routes
│ ├── tests/ # Jest and Supertest files
│ ├── app.ts # Express app setup
│ └── server.ts # Server start file
│
├── .env # Environment variables
├── jest.config.js # Jest configuration
└── package.json2.3. Create the Express App
1. Create app.ts
import express from 'express';
import cors from 'cors';
import todoRoutes from './routes/todoRoutes';
const app = express();
app.use(cors());
app.use(express.json());
app.get('/', (req, res) => res.send('API is running'));
app.use('/api/todos', todoRoutes);
export default app;2. Set Up MongoDB Connection in config/db.ts
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI!);
console.log(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.error(`Error: ${(error as Error).message}`);
process.exit(1);
}
};
export default connectDB;3. Start the Server in server.ts
import dotenv from 'dotenv';
import app from './app';
import connectDB from './config/db';
dotenv.config();
const PORT = process.env.PORT || 5000;
connectDB().then(() => {
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
});2.4. Create the Todo Schema
File: models/Todo.ts
import mongoose, { Schema, Document } from 'mongoose';
export interface ITodo extends Document {
title: string;
completed: boolean;
}
const TodoSchema: Schema = new Schema({
title: { type: String, required: true },
completed: { type: Boolean, default: false },
});
export default mongoose.model<ITodo>('Todo', TodoSchema);2.5. Create Routes and Controllers
File: controllers/todoController.ts
import { Request, Response } from 'express';
import Todo from '../models/Todo';
export const getTodos = async (req: Request, res: Response) => {
const todos = await Todo.find();
res.json(todos);
};
export const createTodo = async (req: Request, res: Response) => {
const { title } = req.body;
const todo = new Todo({ title });
await todo.save();
res.status(201).json(todo);
};
export const deleteTodo = async (req: Request, res: Response) => {
const { id } = req.params;
await Todo.findByIdAndDelete(id);
res.json({ message: 'Todo deleted' });
};File: routes/todoRoutes.ts
import { Router } from 'express';
import { getTodos, createTodo, deleteTodo } from '../controllers/todoController';
const router = Router();
router.get('/', getTodos);
router.post('/', createTodo);
router.delete('/:id', deleteTodo);
export default router;3. Testing the Backend
We’ll use Jest, Supertest, and MongoMemoryServer for isolated testing.
3.1. Jest Configuration
Add this to package.json:
"scripts": {
"test": "jest --config jest.config.js"
}Create jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/tests/**/*.test.ts'],
};3.2. Write Tests
File: tests/todo.test.ts
import request from 'supertest';
import app from '../app';
import mongoose from 'mongoose';
import { MongoMemoryServer } from 'mongodb-memory-server';
import Todo from '../models/Todo';
let mongoServer: MongoMemoryServer;
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
await mongoose.connect(uri);
});
afterAll(async () => {
await mongoose.disconnect();
await mongoServer.stop();
});
describe('Todo API', () => {
it('should create a new todo', async () => {
const res = await request(app).post('/api/todos').send({ title: 'Test Todo' });
expect(res.status).toBe(201);
expect(res.body.title).toBe('Test Todo');
});
it('should retrieve all todos', async () => {
const res = await request(app).get('/api/todos');
expect(res.status).toBe(200);
expect(res.body.length).toBe(1);
});
it('should delete a todo', async () => {
const todo = await Todo.create({ title: 'To Delete' });
const res = await request(app).delete(`/api/todos/${todo._id}`);
expect(res.status).toBe(200);
expect(res.body.message).toBe('Todo deleted');
});
});4. Running the Backend
- Start the backend server:
npm run dev- Run the tests:
npm test5. Homework Assignment
- Write an additional route to update a todo’s status (completed/not completed).
- Add tests for this new route in the test file.
- Ensure all tests pass successfully.
Next Lesson Preview: Lesson 6
In Lesson 6, we will:
- Integrate the OpenAI API into our Express backend.
- Build endpoints to generate AI-powered descriptions, schedules, and subtasks.
