Skip to content

Commit 8234ec7

Browse files
feat: set up repo structure and backend connection (#1)
* Set up repo structure and backend connection
1 parent 5dc8f4d commit 8234ec7

11 files changed

Lines changed: 754 additions & 0 deletions

File tree

.gitignore

Whitespace-only changes.

server/express/.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# MongoDB Configuration
2+
# Replace with your MongoDB Atlas connection string
3+
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
4+
5+
# Server Configuration
6+
PORT=3001
7+
NODE_ENV=development
8+
9+
# CORS Configuration (frontend URL)
10+
CORS_ORIGIN=http://localhost:3000

server/express/.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Dependencies
2+
node_modules/
3+
npm-debug.log*
4+
package-lock.json
5+
6+
# Environment variables
7+
.env
8+
9+
# Build output
10+
dist/
11+
12+
# TypeScript
13+
*.tsbuildinfo
14+
15+
# Logs
16+
logs
17+
*.log
18+
19+
# Test coverage
20+
coverage/
21+
22+
# Optional npm cache directory
23+
.npm
24+
25+
# macOS
26+
.DS_Store

server/express/package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "sample-mflix-express-backend",
3+
"version": "1.0.0",
4+
"description": "Express.js backend for MongoDB sample mflix application demonstrating CRUD operations, aggregations, search, and geospatial queries",
5+
"license": "Apache-2.0",
6+
"author": "Jordan Smith",
7+
"type": "commonjs",
8+
"main": "dist/app.ts",
9+
"scripts": {
10+
"build": "tsc",
11+
"start": "node dist/app.js",
12+
"dev": "ts-node src/app.ts",
13+
"test-setup": "ts-node src/test-setup.ts",
14+
"test": "echo \"Error: no test specified\" && exit 1"
15+
},
16+
"dependencies": {
17+
"express": "^5.1.0",
18+
"mongodb": "^6.3.0",
19+
"dotenv": "^16.3.1",
20+
"cors": "^2.8.5"
21+
},
22+
"devDependencies": {
23+
"@types/express": "^4.17.21",
24+
"@types/node": "^20.10.5",
25+
"@types/cors": "^2.8.17",
26+
"typescript": "^5.3.3",
27+
"ts-node": "^10.9.2"
28+
}
29+
}

server/express/src/app.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* Express.js Backend for MongoDB Sample MFlix Application
3+
*
4+
* This application demonstrates MongoDB operations using the Node.js driver
5+
* with TypeScript. The code prioritizes readability and educational value
6+
* over performance optimization.
7+
*/
8+
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';
15+
16+
// Load environment variables from .env file
17+
// This must be called before any other imports that use environment variables
18+
dotenv.config();
19+
20+
const app = express();
21+
const PORT = process.env.PORT || 3001;
22+
23+
/**
24+
* CORS Configuration
25+
* Allows the frontend to communicate with this Express backend
26+
* In production, this should be configured to only allow specific origins
27+
*/
28+
app.use(cors({
29+
origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
30+
credentials: true
31+
}));
32+
33+
/**
34+
* Middleware Configuration
35+
* Express.json() parses incoming JSON requests and puts the parsed data in req.body
36+
* The limit is set to handle potentially large movie documents
37+
*/
38+
app.use(express.json({ limit: '10mb' }));
39+
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
40+
41+
/**
42+
* API Routes
43+
* All movie-related CRUD operations are handled by the movies router
44+
*/
45+
app.use('/api/movies', moviesRouter);
46+
47+
/**
48+
* Root Endpoint
49+
* Provides basic information about the API
50+
*/
51+
app.get('/', (req, res) => {
52+
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',
56+
endpoints: {
57+
movies: '/api/movies'
58+
}
59+
});
60+
});
61+
62+
/**
63+
* Global Error Handler
64+
* This middleware catches any unhandled errors and returns a consistent error response
65+
* It should be the last middleware in the chain
66+
*/
67+
app.use(errorHandler);
68+
69+
/**
70+
* Application Startup Function
71+
* Handles database connection, requirement verification, and server startup
72+
*/
73+
async function startServer() {
74+
try {
75+
console.log('Starting MongoDB Sample MFlix API...');
76+
77+
// Connect to MongoDB database
78+
console.log('Connecting to MongoDB...');
79+
await connectToDatabase();
80+
console.log('Connected to MongoDB successfully');
81+
82+
// Verify that all required indexes and sample data exist
83+
console.log('Verifying requirements (indexes and sample data)...');
84+
await verifyRequirements();
85+
console.log('All requirements verified successfully');
86+
87+
// Start the Express server
88+
app.listen(PORT, () => {
89+
console.log(`Server running on port ${PORT}`);
90+
console.log(`API documentation available at http://localhost:${PORT}`);
91+
});
92+
93+
} catch (error) {
94+
console.error('Failed to start server:', error);
95+
96+
// Exit the process if we can't start properly
97+
// This ensures the application doesn't run in a broken state
98+
process.exit(1);
99+
}
100+
}
101+
102+
/**
103+
* Graceful Shutdown Handler
104+
* Ensures the application shuts down cleanly when terminated
105+
*/
106+
process.on('SIGINT', () => {
107+
console.log('\nReceived SIGINT. Shutting down...');
108+
closeDatabaseConnection();
109+
process.exit(0);
110+
});
111+
112+
process.on('SIGTERM', () => {
113+
console.log('\nReceived SIGTERM. Shutting down...');
114+
closeDatabaseConnection();
115+
process.exit(0);
116+
});
117+
118+
// Start the server
119+
startServer();
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Database Configuration and Connection Management
3+
*
4+
* This module handles MongoDB connection setup using the Node.js driver
5+
* and implements pre-flight checks to ensure the application has all
6+
* necessary indexes and sample data.
7+
*/
8+
9+
import { MongoClient, Db, Collection, Document } from 'mongodb';
10+
11+
let client: MongoClient;
12+
let database: Db;
13+
14+
async function _connectToDatabase(): Promise<Db> {
15+
// Return existing connection if already established
16+
// This prevents creating multiple connections unnecessarily
17+
if (database) {
18+
return database;
19+
}
20+
21+
// Retrieve MongoDB connection string from environment variables
22+
const uri = process.env.MONGODB_URI;
23+
24+
if (!uri) {
25+
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.'
27+
);
28+
}
29+
30+
try {
31+
// Create new MongoDB client instance
32+
client = new MongoClient(uri);
33+
34+
// Connect to MongoDB
35+
await client.connect();
36+
37+
// Get reference to the sample_mflix database
38+
database = client.db('sample_mflix');
39+
40+
console.log(`Connected to database: ${database.databaseName}`);
41+
42+
return database;
43+
44+
} catch (error) {
45+
throw error;
46+
}
47+
}
48+
49+
let connect$: Promise<Db>;
50+
/**
51+
* Establishes connection to MongoDB by using the connection string from environment variables
52+
*
53+
* @returns Promise<Db> - The connected database instance
54+
* @throws Error if connection fails or if MONGODB_URI is not provided
55+
*/
56+
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$;
60+
}
61+
62+
/**
63+
* Gets a reference to a specific collection in the database
64+
*
65+
* @param collectionName - Name of the collection to access
66+
* @returns Collection instance
67+
* @throws Error if database is not connected
68+
*/
69+
export function getCollection<T extends Document>(collectionName: string): Collection<T> {
70+
if (!database) {
71+
throw new Error(
72+
'Database not connected.'
73+
);
74+
}
75+
76+
return database.collection(collectionName);
77+
}
78+
79+
/**
80+
* Closes the database connection
81+
* This should be called when the application is shutting down
82+
*/
83+
export async function closeDatabaseConnection(): Promise<void> {
84+
if (client) {
85+
await client.close();
86+
console.log('Database connection closed');
87+
}
88+
}
89+
90+
/**
91+
* Verifies that all required indexes exist and sample data is present
92+
*
93+
* If any requirements are missing, this function will attempt to create them.
94+
*/
95+
export async function verifyRequirements(): Promise<void> {
96+
try {
97+
const db = await connectToDatabase();
98+
99+
// Check if the movies collection exists and has data
100+
await verifyMoviesCollection(db);
101+
console.log('All database requirements verified successfully');
102+
103+
} catch (error) {
104+
console.error('Requirements verification failed:', error);
105+
throw error;
106+
}
107+
}
108+
109+
/**
110+
* Verifies the movies collection and creates necessary indexes
111+
*/
112+
async function verifyMoviesCollection(db: Db): Promise<void> {
113+
const moviesCollection = db.collection('movies');
114+
115+
// Check if collection has documents
116+
const movieCount = await moviesCollection.estimatedDocumentCount();
117+
118+
if (movieCount === 0) {
119+
console.warn('Movies collection is empty. Please ensure sample_mflix data is loaded.');
120+
}
121+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Movie Controller
3+
*/
4+
5+
import { Request, Response } from 'express';
6+
import { getCollection } from '../config/database';
7+
import { createSuccessResponse } from '../utils/errorHandler';
8+
9+
10+
/**
11+
* GET /api/movies
12+
*/
13+
export async function getAllMovies(req: Request, res: Response): Promise<void> {
14+
const moviesCollection = getCollection('movies');
15+
16+
try {
17+
// Execute the find operation with all options
18+
const movies = await moviesCollection
19+
.find({})
20+
.limit(10) // TODO: Remove temp limit used for testing
21+
.toArray();
22+
23+
// Return successful response
24+
res.json(createSuccessResponse(movies, `Found ${movies.length} movies`));
25+
26+
} catch (error) {
27+
throw error;
28+
}
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Movies API Routes
3+
*/
4+
5+
import express from 'express';
6+
import { asyncHandler } from '../utils/errorHandler';
7+
import * as movieController from '../controllers/movieController';
8+
9+
const router = express.Router();
10+
11+
/**
12+
* GET /api/movies
13+
*
14+
* Retrieves multiple movies with optional filtering, sorting, and pagination.
15+
* Demonstrates the find() operation with various query options.
16+
*/
17+
router.get('/', asyncHandler(movieController.getAllMovies));
18+
19+
export default router;

0 commit comments

Comments
 (0)