Skip to content

Commit

Permalink
error handling fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Victor committed Aug 5, 2022
1 parent 7d36706 commit 82c07f0
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 347 deletions.
2 changes: 1 addition & 1 deletion docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ services:
build:
context: .
target: build
# image: league-api
image: league-api
ports:
- 3000:3000
env_file: .env
Expand Down
6 changes: 1 addition & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ services:
build:
context: .
target: production
# image: registry.heroku.com/safe-badlands-10026/web
# volumes:
# - ./:/usr/src/app
# - /usr/src/app/node_modules
image: registry.heroku.com/infinite-woodland-13248/web
env_file: .env
ports:
- 3000:3000
# command: npm run dev
10 changes: 10 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
NODE_ENV=test
PORT=
DATABASE_URL=mongodb://localhost:27017/your-db

JWT_SECRET=supersecret
JWT_EXPIRES_IN=4h
COOKIE_SECRET=somesecret
REDIS_URL=redis://127.0.0.1:6379
BASE_URL=http://localhost:3000/league

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"description": "",
"main": "index.js",
"scripts": {
"start": "set NODE_ENV=production&& npm run build && node dist/src/app.js",
"dev": "set NODE_ENV=development&& nodemon src/app.ts",
"start": "npm run build && node dist/src/app.js",
"dev": "NODE_ENV=development nodemon src/app.ts",
"build": "rimraf ./dist && tsc -p .",
"test": "jest --watch"
},
Expand Down
5 changes: 3 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require('./cache/cacheService');
const app: Application = express();

let RedisStore = connectRedis(session);
const redisClient = new Redis();
const redisClient = new Redis(configuration().redis.url);

// if running behind a proxy
// app.set('trust proxy', 1)
Expand Down Expand Up @@ -78,7 +78,7 @@ app.use('/api/v1/team', teamRouter);
app.use('/api/v1/fixture', fixtureRouter);

app.get('/', (req: Request, res: Response, next: NextFunction) => {
res.send('Hello world!');
res.send('League API!');
});

const port = configuration().port;
Expand All @@ -89,6 +89,7 @@ app.listen(port, async () => {
});

app.all('*', (req, res, next) => {
configuration().env;
next(new AppError(`Can't find ${req.originalUrl} on this server`, 404));
});

Expand Down
8 changes: 4 additions & 4 deletions src/cache/cacheService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import mongoose from 'mongoose';
import { promisify } from 'util';

import logger from '../logger';
import { configuration } from '../../config/default';

const client = new Redis();
const client = new Redis(configuration().redis.url);
client.hget = promisify(client.hget) as any;

const exec = mongoose.Query.prototype.exec;
Expand Down Expand Up @@ -39,10 +40,9 @@ mongoose.Query.prototype.exec = async function () {
// Check if we have a value for 'key' in redis
const cacheValue = await client.hget(this.hashKey, key);

// // If we do, serve datat from cache instead of running the query
// // If we do, serve data from cache instead of running the query
if (cacheValue) {
console.log('Serving from cache');

logger.info('Serving from cache');
const doc = JSON.parse(cacheValue);

return Array.isArray(doc)
Expand Down
160 changes: 55 additions & 105 deletions src/fixtures/controllers/fixturesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,130 +16,80 @@ import {

export const createFixtures = catchAsync(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { home, away }: Fixture = req.body;
const user = clone(req.user);
const payload = {
home,
away,
createdBy: user?.id,
} as Fixture;

const data = await createFixture(payload);

res.status(201).json({
message: 'Fixtures created successfully',
status: 'success',
data,
});
} catch (error: any) {
logger.error(
`Error occurred while creating fixtures: ${JSON.stringify(error)}`
);
res.status(error.statusCode || 500).json({
status: error.status || 'error',
message: error.message,
});
}
const { home, away }: Fixture = req.body;
const user = clone(req.user);
const payload = {
home,
away,
createdBy: user?.id,
} as Fixture;

const data = await createFixture(payload);

res.status(201).json({
message: 'Fixtures created successfully',
status: 'success',
data,
});
}
);

export const findFixtureById = catchAsync(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { id } = req.params;

const data = await findOne(id);

res.status(200).json({
message: 'Fixture fetched successfully',
status: 'success',
data,
});
} catch (error: any) {
logger.error(
`Error occurred while fetching fixtures: ${JSON.stringify(error)}`
);
res.status(error.statusCode || 500).json({
status: error.status || 'error',
message: error.message,
});
}
const { id } = req.params;

const data = await findOne(id);

res.status(200).json({
message: 'Fixture fetched successfully',
status: 'success',
data,
});
}
);

export const findFixtures = catchAsync(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { status } = req.query;

const data = await find(status as FixtureStatues);

res.status(200).json({
message: 'Fixtures fetched successfully',
status: 'success',
data,
});
} catch (error: any) {
logger.error(
`Error occurred while fetching fixtures: ${JSON.stringify(error)}`
);
res.status(error.statusCode || 500).json({
status: error.status || 'error',
message: error.message,
});
}
const { status } = req.query;

const data = await find(status as FixtureStatues);

res.status(200).json({
message: 'Fixtures fetched successfully',
status: 'success',
data,
});
}
);

export const updateFixture = catchAsync(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { id } = req.params;
const { status } = req.body;

const data = await update(id, status);

res.status(200).json({
message: 'Fixture updated successfully',
status: 'success',
data,
});
} catch (error: any) {
logger.error(
`Error occurred while updating fixture: ${JSON.stringify(error)}`
);
res.status(error.statusCode || 500).json({
status: error.status || 'error',
message: error.message,
});
}
const { id } = req.params;
const { status } = req.body;

const data = await update(id, status);

res.status(200).json({
message: 'Fixture updated successfully',
status: 'success',
data,
});
}
);

export const removeFixture = catchAsync(
async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const { id } = req.params;

const payload: Partial<Fixture> = {
isDeleted: true,
deletedAt: moment().toDate(),
};
await remove(id, payload);

res.status(204).json({
message: 'Fixture removed successfully',
status: 'success',
});
} catch (error: any) {
logger.error(
`Error occurred while removing fixture: ${JSON.stringify(error)}`
);
res.status(error.statusCode || 500).json({
status: error.status || 'error',
message: error.message,
});
}
const { id } = req.params;

const payload: Partial<Fixture> = {
isDeleted: true,
deletedAt: moment().toDate(),
};
await remove(id, payload);

res.status(204).json({
message: 'Fixture removed successfully',
status: 'success',
});
}
);
30 changes: 23 additions & 7 deletions src/middlewares/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
import { ErrorRequestHandler, NextFunction, Request, Response } from 'express';
import AppError from '../utils/appError';
import { configuration } from '../../config/default';
import logger from '../logger';

// handle cast errors from mongoose
const handleCastErrorDB = (err: any) => {
const message = `Invalid ${err.path}: ${err.value}.`;
return new AppError(message, 400);
};

// handle duplicate key errors from mongoose
const handleDuplicateFieldDB = (err: any) => {
const value = err.errmsg.match(/(["'])(\\?.)*?\1/)[0];
const value = err.message.match(/(["'])(\\?.)*?\1/)[0];
const message = `Duplicate field value ${value} . Pls use another value`;
return new AppError(message, 400);
return new AppError(message, 409);
};

// handle validaion errors
const handleValidationErrorDB = (err: any) => {
const errors = Object.values(err.errors).map((el: any) => el.message);
const message = `Invalid input data. ${errors.join('. ')}`;
return new AppError(message, 400);
};

// Handle invalid JWT token errors
const handleJWTError = () =>
new AppError('Invalid token, pls login again!', 401);

// Handle JWT expired errors
const handleJWTExpiredError = () =>
new AppError('Your token has expired! Pls login again', 401);

// Structure Error format in Development
const sendErrorDev = (err: any, res: Response) => {
res.status(err.statusCode).json({
status: err.status,
Expand All @@ -33,6 +41,7 @@ const sendErrorDev = (err: any, res: Response) => {
});
};

// Structure Error format in Production
const sendErrorProd = (err: any, res: Response) => {
// Operational, trusted error: send message to client
if (err.isOperational) {
Expand All @@ -41,9 +50,9 @@ const sendErrorProd = (err: any, res: Response) => {
message: err.message,
});
} else {
// Programming or unknown error: don't leak details
// Unhandled,Programming, or unknown error: don't leak error details to user
// Log Error
console.error('Error 💥:', err);
logger.error('Error 💥:', err);

// Send generic message
res.status(500).json({
Expand All @@ -53,17 +62,24 @@ const sendErrorProd = (err: any, res: Response) => {
}
};

// Global error handler
export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';

if (process.env.NODE_ENV === 'development') {
if (configuration().env === 'development') {
// Send development error
sendErrorDev(err, res);
} else if (process.env.NODE_ENV === 'production') {
} else if (configuration().env === 'staging') {
// Send production error
let error = { ...err };
error.message = err.message;

if (error.name === 'CastError') error = handleCastErrorDB(error);
console.log({ error });

if (error.message.includes('Cast to ObjectId')) {
error = handleCastErrorDB(error);
}
if (error.code === 11000) error = handleDuplicateFieldDB(error);
if (error.name === 'ValidationError')
error = handleValidationErrorDB(error);
Expand Down
Loading

0 comments on commit 82c07f0

Please sign in to comment.