Skip to content

Commit 0dac316

Browse files
feat: implement CRUD API w/ testing
1 parent 8234ec7 commit 0dac316

11 files changed

Lines changed: 1488 additions & 172 deletions

File tree

server/express/jest.config.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"preset": "ts-jest",
3+
"testEnvironment": "node",
4+
"roots": ["<rootDir>/src", "<rootDir>/tests"],
5+
"testMatch": [
6+
"**/__tests__/**/*.ts",
7+
"**/?(*.)+(spec|test).ts"
8+
],
9+
"transform": {
10+
"^.+\\.ts$": "ts-jest"
11+
},
12+
"collectCoverageFrom": [
13+
"src/**/*.ts",
14+
"!src/**/*.d.ts"
15+
],
16+
"coverageDirectory": "coverage",
17+
"coverageReporters": [
18+
"text",
19+
"lcov",
20+
"html"
21+
],
22+
"setupFilesAfterEnv": ["<rootDir>/tests/setup.ts"]
23+
}

server/express/package.json

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,29 @@
55
"license": "Apache-2.0",
66
"author": "Jordan Smith",
77
"type": "commonjs",
8-
"main": "dist/app.ts",
8+
"main": "dist/app.js",
99
"scripts": {
1010
"build": "tsc",
11-
"start": "node dist/app.js",
11+
"start": "node dist/src/app.js",
1212
"dev": "ts-node src/app.ts",
13-
"test-setup": "ts-node src/test-setup.ts",
14-
"test": "echo \"Error: no test specified\" && exit 1"
13+
"test": "jest",
14+
"test:watch": "jest --watch",
15+
"test:coverage": "jest --coverage"
1516
},
1617
"dependencies": {
17-
"express": "^5.1.0",
18-
"mongodb": "^6.3.0",
18+
"cors": "^2.8.5",
1919
"dotenv": "^16.3.1",
20-
"cors": "^2.8.5"
20+
"express": "^5.1.0",
21+
"mongodb": "^6.3.0"
2122
},
2223
"devDependencies": {
24+
"@types/cors": "^2.8.17",
2325
"@types/express": "^4.17.21",
26+
"@types/jest": "^29.5.14",
2427
"@types/node": "^20.10.5",
25-
"@types/cors": "^2.8.17",
26-
"typescript": "^5.3.3",
27-
"ts-node": "^10.9.2"
28+
"jest": "^29.7.0",
29+
"ts-jest": "^29.1.1",
30+
"ts-node": "^10.9.2",
31+
"typescript": "^5.3.3"
2832
}
2933
}

server/express/src/app.ts

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
/**
22
* Express.js Backend for MongoDB Sample MFlix Application
3-
*
3+
*
44
* This application demonstrates MongoDB operations using the Node.js driver
55
* with TypeScript. The code prioritizes readability and educational value
66
* over performance optimization.
77
*/
88

9-
import express from 'express';
10-
import cors from 'cors';
11-
import dotenv from 'dotenv';
12-
import { closeDatabaseConnection, connectToDatabase, verifyRequirements } from './config/database';
13-
import { errorHandler } from './utils/errorHandler';
14-
import moviesRouter from './routes/movies';
9+
import express from "express";
10+
import cors from "cors";
11+
import dotenv from "dotenv";
12+
import {
13+
closeDatabaseConnection,
14+
connectToDatabase,
15+
verifyRequirements,
16+
} from "./config/database";
17+
import { errorHandler } from "./utils/errorHandler";
18+
import moviesRouter from "./routes/movies";
1519

1620
// Load environment variables from .env file
1721
// This must be called before any other imports that use environment variables
@@ -25,37 +29,40 @@ const PORT = process.env.PORT || 3001;
2529
* Allows the frontend to communicate with this Express backend
2630
* In production, this should be configured to only allow specific origins
2731
*/
28-
app.use(cors({
29-
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
30-
credentials: true
31-
}));
32+
app.use(
33+
cors({
34+
origin: process.env.CORS_ORIGIN || "http://localhost:3000",
35+
credentials: true,
36+
})
37+
);
3238

3339
/**
3440
* Middleware Configuration
3541
* Express.json() parses incoming JSON requests and puts the parsed data in req.body
3642
* The limit is set to handle potentially large movie documents
3743
*/
38-
app.use(express.json({ limit: '10mb' }));
39-
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
44+
app.use(express.json({ limit: "10mb" }));
45+
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
4046

4147
/**
4248
* API Routes
4349
* All movie-related CRUD operations are handled by the movies router
4450
*/
45-
app.use('/api/movies', moviesRouter);
51+
app.use("/api/movies", moviesRouter);
4652

4753
/**
4854
* Root Endpoint
4955
* Provides basic information about the API
5056
*/
51-
app.get('/', (req, res) => {
57+
app.get("/", (req, res) => {
5258
res.json({
53-
name: 'MongoDB Sample MFlix API',
54-
version: '1.0.0',
55-
description: 'Express.js backend demonstrating MongoDB operations with the sample_mflix dataset',
59+
name: "MongoDB Sample MFlix API",
60+
version: "1.0.0",
61+
description:
62+
"Express.js backend demonstrating MongoDB operations with the sample_mflix dataset",
5663
endpoints: {
57-
movies: '/api/movies'
58-
}
64+
movies: "/api/movies",
65+
},
5966
});
6067
});
6168

@@ -72,27 +79,26 @@ app.use(errorHandler);
7279
*/
7380
async function startServer() {
7481
try {
75-
console.log('Starting MongoDB Sample MFlix API...');
76-
82+
console.log("Starting MongoDB Sample MFlix API...");
83+
7784
// Connect to MongoDB database
78-
console.log('Connecting to MongoDB...');
85+
console.log("Connecting to MongoDB...");
7986
await connectToDatabase();
80-
console.log('Connected to MongoDB successfully');
81-
87+
console.log("Connected to MongoDB successfully");
88+
8289
// Verify that all required indexes and sample data exist
83-
console.log('Verifying requirements (indexes and sample data)...');
90+
console.log("Verifying requirements (indexes and sample data)...");
8491
await verifyRequirements();
85-
console.log('All requirements verified successfully');
86-
92+
console.log("All requirements verified successfully");
93+
8794
// Start the Express server
8895
app.listen(PORT, () => {
8996
console.log(`Server running on port ${PORT}`);
9097
console.log(`API documentation available at http://localhost:${PORT}`);
9198
});
92-
9399
} catch (error) {
94-
console.error('Failed to start server:', error);
95-
100+
console.error("Failed to start server:", error);
101+
96102
// Exit the process if we can't start properly
97103
// This ensures the application doesn't run in a broken state
98104
process.exit(1);
@@ -103,17 +109,17 @@ async function startServer() {
103109
* Graceful Shutdown Handler
104110
* Ensures the application shuts down cleanly when terminated
105111
*/
106-
process.on('SIGINT', () => {
107-
console.log('\nReceived SIGINT. Shutting down...');
112+
process.on("SIGINT", () => {
113+
console.log("\nReceived SIGINT. Shutting down...");
108114
closeDatabaseConnection();
109115
process.exit(0);
110116
});
111117

112-
process.on('SIGTERM', () => {
113-
console.log('\nReceived SIGTERM. Shutting down...');
118+
process.on("SIGTERM", () => {
119+
console.log("\nReceived SIGTERM. Shutting down...");
114120
closeDatabaseConnection();
115121
process.exit(0);
116122
});
117123

118124
// Start the server
119-
startServer();
125+
startServer();
Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/**
22
* Database Configuration and Connection Management
3-
*
3+
*
44
* This module handles MongoDB connection setup using the Node.js driver
55
* and implements pre-flight checks to ensure the application has all
66
* necessary indexes and sample data.
77
*/
88

9-
import { MongoClient, Db, Collection, Document } from 'mongodb';
9+
import { MongoClient, Db, Collection, Document } from "mongodb";
1010

1111
let client: MongoClient;
1212
let database: Db;
@@ -20,27 +20,26 @@ async function _connectToDatabase(): Promise<Db> {
2020

2121
// Retrieve MongoDB connection string from environment variables
2222
const uri = process.env.MONGODB_URI;
23-
23+
2424
if (!uri) {
2525
throw new Error(
26-
'MONGODB_URI environment variable is not defined. Please check your .env file and ensure it contains a valid MongoDB connection string.'
26+
"MONGODB_URI environment variable is not defined. Please check your .env file and ensure it contains a valid MongoDB connection string."
2727
);
2828
}
2929

3030
try {
3131
// Create new MongoDB client instance
3232
client = new MongoClient(uri);
33-
33+
3434
// Connect to MongoDB
3535
await client.connect();
36-
36+
3737
// Get reference to the sample_mflix database
38-
database = client.db('sample_mflix');
39-
38+
database = client.db("sample_mflix");
39+
4040
console.log(`Connected to database: ${database.databaseName}`);
41-
41+
4242
return database;
43-
4443
} catch (error) {
4544
throw error;
4645
}
@@ -49,30 +48,30 @@ async function _connectToDatabase(): Promise<Db> {
4948
let connect$: Promise<Db>;
5049
/**
5150
* Establishes connection to MongoDB by using the connection string from environment variables
52-
*
51+
*
5352
* @returns Promise<Db> - The connected database instance
5453
* @throws Error if connection fails or if MONGODB_URI is not provided
5554
*/
5655
export async function connectToDatabase(): Promise<Db> {
57-
// connect$ only gets assigned exactly once on the first request, ensuring all subsequent requests use the same connect$ promise.
58-
connect$ ??= _connectToDatabase();
59-
return await connect$;
56+
// connect$ only gets assigned exactly once on the first request, ensuring all subsequent requests use the same connect$ promise.
57+
connect$ ??= _connectToDatabase();
58+
return await connect$;
6059
}
6160

6261
/**
6362
* Gets a reference to a specific collection in the database
64-
*
63+
*
6564
* @param collectionName - Name of the collection to access
6665
* @returns Collection instance
6766
* @throws Error if database is not connected
6867
*/
69-
export function getCollection<T extends Document>(collectionName: string): Collection<T> {
68+
export function getCollection<T extends Document>(
69+
collectionName: string
70+
): Collection<T> {
7071
if (!database) {
71-
throw new Error(
72-
'Database not connected.'
73-
);
72+
throw new Error("Database not connected.");
7473
}
75-
74+
7675
return database.collection(collectionName);
7776
}
7877

@@ -83,25 +82,24 @@ export function getCollection<T extends Document>(collectionName: string): Colle
8382
export async function closeDatabaseConnection(): Promise<void> {
8483
if (client) {
8584
await client.close();
86-
console.log('Database connection closed');
85+
console.log("Database connection closed");
8786
}
8887
}
8988

9089
/**
9190
* Verifies that all required indexes exist and sample data is present
92-
*
91+
*
9392
* If any requirements are missing, this function will attempt to create them.
9493
*/
9594
export async function verifyRequirements(): Promise<void> {
9695
try {
9796
const db = await connectToDatabase();
98-
97+
9998
// Check if the movies collection exists and has data
10099
await verifyMoviesCollection(db);
101-
console.log('All database requirements verified successfully');
102-
100+
console.log("All database requirements verified successfully");
103101
} catch (error) {
104-
console.error('Requirements verification failed:', error);
102+
console.error("Requirements verification failed:", error);
105103
throw error;
106104
}
107105
}
@@ -110,12 +108,28 @@ export async function verifyRequirements(): Promise<void> {
110108
* Verifies the movies collection and creates necessary indexes
111109
*/
112110
async function verifyMoviesCollection(db: Db): Promise<void> {
113-
const moviesCollection = db.collection('movies');
114-
111+
const moviesCollection = db.collection("movies");
112+
115113
// Check if collection has documents
116114
const movieCount = await moviesCollection.estimatedDocumentCount();
117-
115+
118116
if (movieCount === 0) {
119-
console.warn('Movies collection is empty. Please ensure sample_mflix data is loaded.');
117+
console.warn(
118+
"Movies collection is empty. Please ensure sample_mflix data is loaded."
119+
);
120+
}
121+
122+
// Create text search index on plot field for full-text search
123+
try {
124+
await moviesCollection.createIndex(
125+
{ plot: "text", title: "text", fullplot: "text" },
126+
{
127+
name: "text_search_index",
128+
background: true,
129+
}
130+
);
131+
console.log("Text search index created for movies collection");
132+
} catch (error) {
133+
console.error("Could not create text search index:", error);
120134
}
121135
}

0 commit comments

Comments
 (0)