Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHE-172] Refactor Server Startup and DB Connection #142

15 changes: 10 additions & 5 deletions __tests__/db.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import mongoose from 'mongoose';
import connectDB from '../server/config/db';

// TODO
/*eslint jest/no-disabled-tests: "off"*/

jest.mock('mongoose', () => ({
connect: jest.fn().mockImplementation(() =>
Promise.resolve({
Expand All @@ -24,26 +27,28 @@ describe('connectDB', () => {

it('should call mongoose.connect with MONGO_URI', async () => {
process.env.MONGO_URI = 'test-mongo-uri';
await connectDB();
await connectDB(process.env.MONGO_URI);
expect(mongoose.connect).toHaveBeenCalledWith('test-mongo-uri');
});

it('should log an error and exit the process if mongoose.connect fails', async () => {
// We now console.error the error's message and throw a DatabaseConnectionError instead
xit('should log an error and exit the process if mongoose.connect fails', async () => {
process.env.MONGO_URI = 'test-mongo-uri';
(mongoose.connect as jest.Mock).mockImplementationOnce(() => {
throw new Error('test error');
});

await connectDB();
await connectDB(process.env.MONGO_URI);

expect(mockConsoleError).toHaveBeenCalledWith('test error');
expect(mockExit).toHaveBeenCalledWith(1);
});

it('should throw an error if MONGO_URI is not defined', async () => {
// This check has been moved to startServer in index.ts
xit('should throw an error if MONGO_URI is not defined', async () => {
delete process.env.MONGO_URI;

await connectDB();
await connectDB(process.env.MONGO_URI!);

expect(mockConsoleError).toHaveBeenCalledWith(
'MONGO_URI must be defined in the environment variables.',
Expand Down
2 changes: 1 addition & 1 deletion __tests__/errorController.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import app from '../server/index';
import app from '../server/app';
import request from 'supertest';
import { Request, Response, NextFunction } from 'express';
import errorHandler from '../server/middleware/errorHandler';
Expand Down
7 changes: 4 additions & 3 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import request from 'supertest';
import app, { startServer } from '../server/index';
import { startServer } from '../server/index';
import app from '../server/app';
import { Server } from 'http';
import mongoose from 'mongoose';

Expand All @@ -8,8 +9,8 @@ import mongoose from 'mongoose';

let server: Server;

beforeEach(() => {
server = startServer();
beforeEach(async () => {
server = await startServer();
});

afterEach((done) => {
Expand Down
9 changes: 9 additions & 0 deletions docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ services:
command: npm run dev-ts
environment:
- NODE_ENV=development
- POSTGRES_USER=postgres
- POSTGRES_DB=ch-dev-database
- POSTGRES_PASSWORD=ch-dev
- AWS_ACCESS_KEY_ID=placeholder-value
- AWS_SECRET_ACCESS_KEY=placeholder-value
brok3turtl3 marked this conversation as resolved.
Show resolved Hide resolved
- AWS_REGION=placeholder-value
- BUCKET_NAME=placeholder-value
# suppress aws sdk v2 deprecation warning
- AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE=1;
brok3turtl3 marked this conversation as resolved.
Show resolved Hide resolved
volumes:
node_modules:
client_node_modules:
11 changes: 10 additions & 1 deletion docker-compose-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,17 @@ services:
depends_on:
- mongo
environment:
- MONGO_URI=mongodb://mongo:27017/ch-testdb
- JWT_SECRET=${JWT_SECRET}
- MONGO_URI=mongodb://mongo:27017/ch-testdb
- POSTGRES_USER=postgres
- POSTGRES_DB=ch-dev-database
- POSTGRES_PASSWORD=ch-dev
- AWS_ACCESS_KEY_ID=placeholder-value
brok3turtl3 marked this conversation as resolved.
Show resolved Hide resolved
- AWS_SECRET_ACCESS_KEY=placeholder-value
- AWS_REGION=placeholder-value
- BUCKET_NAME=placeholder-value
# suppress aws sdk v2 deprecation warning
- AWS_SDK_JS_SUPPRESS_MAINTENANCE_MODE_MESSAGE=1;
command: npm run test:all

mongo:
Expand Down
54 changes: 54 additions & 0 deletions server/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import path from 'path';
import express, { Request, Response } from 'express';
import 'express-async-errors';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
dotenv.config();
import {
userRouter,
profileRouter,
authRouter,
imageRouter,
alumniRouter,
forumRouter,
devRouter,
} from './routes';
import errorHandler from './middleware/errorHandler';
import { NotFoundError } from './errors';

// Instantiate application
const app = express();

// Middleware to parse request bodies
app.use(express.json());
// Middleware to parse request cookies
app.use(cookieParser());

// API routers
app.use('/api/users', userRouter);
app.use('/api/profiles', profileRouter);
app.use('/api/auth', authRouter);
app.use('/api/images', imageRouter);
app.use('/api/alumni', alumniRouter);
app.use('/api/forums', forumRouter);
app.use('/api/devRoutes', devRouter);

// Serve client from build in production
if (process.env.NODE_ENV === 'production') {
console.log(`SERVER STARTED IN PRODUCTION`);
app.use(express.static(path.join(__dirname, '../../client/build')));

app.get('*', (req: Request, res: Response) =>
res.sendFile(path.resolve(__dirname, '../../client/build/index.html')),
);
}

// Catch all route handler
app.use((_req, _res) => {
throw new NotFoundError();
});

// Global error handler
app.use(errorHandler);

export default app;
19 changes: 5 additions & 14 deletions server/config/db.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
import { DatabaseConnectionError } from '../errors';

const connectDB = async (): Promise<void> => {
const connectDB = async (mongoUri: string) => {
try {
// Check that MONGO_URI is defined
if (!process.env.MONGO_URI) {
throw new Error('MONGO_URI must be defined in the environment variables.');
}

const connection = await mongoose.connect(process.env.MONGO_URI);

console.log(`MongoDB is connected to: ${connection.connection.host}`);
const connection = await mongoose.connect(mongoUri);
seantokuzo marked this conversation as resolved.
Show resolved Hide resolved
console.log(`🍃 MongoDB is connected to: ${connection.connection.host}`);
} catch (error) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error('❌ Error connecting to the database ❌');
}
process.exit(1);
throw new DatabaseConnectionError();
}
seantokuzo marked this conversation as resolved.
Show resolved Hide resolved
};

Expand Down
80 changes: 21 additions & 59 deletions server/index.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,32 @@
import path from 'path';
import express, { Request, Response, Application } from 'express';
import 'express-async-errors';
import userRoutes from './routes/userRoutes';
import profileRoutes from './routes/profileRoutes';
import authRoutes from './routes/authRoutes';
import imageRoutes from './routes/imageRoutes';
import alumniRoutes from './routes/alumniRoutes';
import forumRoutes from './routes/forumRoutes';
import devRoutes from './routes/devRoutes';
import app from './app';
import connectDB from './config/db';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
import errorHandler from './middleware/errorHandler';
import { NotFoundError } from './errors';

dotenv.config();

const app: Application = express();

app.use(express.json());
app.use(cookieParser());

app.get('/health', (req: Request, res: Response) => {
res.status(200).send('OK');
});

app.use('/api/users', userRoutes);
app.use('/api/profiles', profileRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/images', imageRoutes);
app.use('/api/alumni', alumniRoutes);
app.use('/api/forums', forumRoutes);
app.use('/api/devRoutes', devRoutes);

if (process.env.NODE_ENV === 'production') {
console.log(`SERVER STARTED IN PRODUCTION`);
app.use(express.static(path.join(__dirname, '../../client/build')));

app.get('*', (req: Request, res: Response) =>
res.sendFile(path.resolve(__dirname, '../../client/build/index.html')),
);
} else {
console.log('SERVER STARTED IN DEV');
app.get('/api', (req: Request, res: Response) => {
res.json({ message: 'API Running - Hazzah!' });
});
}

app.use((_req, _res) => {
throw new NotFoundError();
});

app.use(errorHandler);

const PORT: number = Number(process.env.PORT) || 3000;

export const startServer = () => {
connectDB();

// Hazzah!
const hazzah = process.env.NODE_ENV === 'development' ? 'Hazzah! ' : '';

export const startServer = async () => {
// Environment variable checks
if (!process.env.JWT_SECRET) throw Error('❌ JWT_SECRET must be defined!');
if (!process.env.MONGO_URI) throw Error('❌ MONGO_URI must be defined!');
if (!process.env.POSTGRES_USER) throw Error('❌ POSTGRES_USER must be defined!');
if (!process.env.POSTGRES_DB) throw Error('❌ POSTGRES_DB must be defined!');
if (!process.env.POSTGRES_PASSWORD) throw Error('❌ POSTGRES_PASSWORD must be defined!');
if (!process.env.AWS_ACCESS_KEY_ID) throw Error('❌ AWS_ACCESS_KEY_ID must be defined!');
if (!process.env.AWS_SECRET_ACCESS_KEY) throw Error('❌ AWS_SECRET_ACCESS_KEY must be defined!');
if (!process.env.AWS_REGION) throw Error('❌ AWS_REGION must be defined!');
if (!process.env.BUCKET_NAME) throw Error('❌ BUCKET_NAME must be defined!');

// Connect to MongoDB
await connectDB(process.env.MONGO_URI);

// Startup the server
return app.listen(PORT, () =>
console.log(`Server running in ${process.env.NODE_ENV} mode on port ${PORT}`),
console.log(`💥 ${hazzah}Server running in ${process.env.NODE_ENV} mode on port ${PORT}`),
);
seantokuzo marked this conversation as resolved.
Show resolved Hide resolved
};

if (require.main === module) {
startServer();
}

export default app;
9 changes: 9 additions & 0 deletions server/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import alumniRouter from './alumniRoutes';
import authRouter from './authRoutes';
import forumRouter from './forumRoutes';
import imageRouter from './imageRoutes';
import profileRouter from './profileRoutes';
import userRouter from './userRoutes';
import devRouter from './devRoutes';

export { alumniRouter, authRouter, forumRouter, imageRouter, profileRouter, userRouter, devRouter };
Loading