Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"dependencies": {
"cors": "^2.8.6",
"express": "^5.2.1",
"express-rate-limit": "^8.4.1",
"helmet": "^8.1.0",
"mongodb": "^7.1.0",
"mongoose": "^9.3.2"
},
Expand Down
11 changes: 3 additions & 8 deletions server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@ import dotenv from 'dotenv';

dotenv.config({ path: './.env' });

import express from 'express';
import cors from 'cors';
import connectDB from './db/connection.js';
import developerRoutes from './src/routes/developers.js';
import app from './src/app.js';

// entry point for starting the back-end server

const app = express();
const PORT = process.env.PORT || 5050;

connectDB();

app.use(cors());
app.use(express.json());
app.use('/api/developers', developerRoutes);

app.listen(PORT, () => {
console.log(`server is running on port ${PORT}`);
});
33 changes: 33 additions & 0 deletions server/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import developerRoutes from './routes/developers.js';
import healthRoutes from './routes/health.js';
import { apiRateLimit } from './middleware/rateLimit.js';
import { notFound } from './middleware/notFound.js';
import { errorHandler } from './middleware/errorHandler.js';

// splitting

const app = express();

// security:
app.use(helmet());
app.use(
cors({
origin: process.env.CLIENT_ORIGIN || 'http://localhost:5173',
credentials: true,
})
);
app.use(express.json({ limit: process.env.JSON_BODY_LIMIT || '1mb' }));
app.use('/api', apiRateLimit);
app.use('/api/health', healthRoutes);

// endpoints -- add new endpoints below
app.use('/api/developers', developerRoutes);

// fallbacks
app.use(notFound);
app.use(errorHandler);

export default app;
10 changes: 10 additions & 0 deletions server/src/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ErrorRequestHandler } from 'express';
import { sendError } from '../utils/apiError.js';

export const errorHandler: ErrorRequestHandler = (err, _req, res, _next) => {
if (process.env.NODE_ENV !== 'test') {
console.error(err);
}

sendError(res, 500, 'INTERNAL_ERROR', 'Something went wrong');
};
6 changes: 6 additions & 0 deletions server/src/middleware/notFound.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RequestHandler } from 'express';
import { sendError } from '../utils/apiError.js';

export const notFound: RequestHandler = (_req, res) => {
sendError(res, 404, 'NOT_FOUND', 'Route not found');
};
8 changes: 8 additions & 0 deletions server/src/middleware/rateLimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import rateLimit from 'express-rate-limit';

export const apiRateLimit = rateLimit({
windowMs: 15 * 60 * 1000,
max: 300,
standardHeaders: true,
legacyHeaders: false,
});
19 changes: 14 additions & 5 deletions server/src/routes/developers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Router, Request, Response } from 'express';
import mongoose from 'mongoose';
import Developer, { IDeveloperDocument } from '../models/Developer.js';
import { sendError } from '../utils/apiError.js';

const router = Router();

Expand All @@ -8,17 +10,24 @@ router.get('/', async (_req: Request, res: Response) => {
const developers = await Developer.find();
res.json(developers);
} catch {
res.status(500).json({ message: 'oh hell nah' });
sendError(res, 500, 'INTERNAL_ERROR', 'Unable to fetch developers');
}
});

router.get('/:id', async (req: Request, res: Response) => {
try {
if (!mongoose.isValidObjectId(req.params.id)) {
return sendError(res, 400, 'BAD_REQUEST', 'Invalid id');
}

const developer = await Developer.findById(req.params.id);
if (!developer) return res.status(404).json({ message: 'oh hell nah' });
if (!developer) {
return sendError(res, 404, 'NOT_FOUND', 'Developer not found');
}

res.json(developer);
} catch {
res.status(500).json({ message: 'oh hell nah' });
sendError(res, 500, 'INTERNAL_ERROR', 'Unable to fetch developer');
}
});

Expand All @@ -27,8 +36,8 @@ router.post('/', async (req: Request<object, object, IDeveloperDocument>, res: R
const developer = new Developer(req.body);
await developer.save();
res.status(201).json(developer);
} catch{
res.status(400).json({ message: 'oh hell nah' });
} catch {
sendError(res, 400, 'VALIDATION_ERROR', 'Invalid developer payload');
}
});

Expand Down
12 changes: 12 additions & 0 deletions server/src/routes/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Router } from 'express';

const router = Router();

router.get('/', (_req, res) => {
res.json({
status: 'ok',
service: 'umsa-api',
});
});

export default router;
28 changes: 28 additions & 0 deletions server/src/utils/apiError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Response } from 'express';

export type ApiErrorCode =
| 'BAD_REQUEST'
| 'INTERNAL_ERROR'
| 'NOT_FOUND'
| 'VALIDATION_ERROR';

export type ApiErrorResponse = {
error: {
code: ApiErrorCode;
message: string;
};
};

export const sendError = (
res: Response,
status: number,
code: ApiErrorCode,
message: string
): Response<ApiErrorResponse> => {
return res.status(status).json({
error: {
code,
message,
},
});
};
Loading