From 0d68b3c499037c443902c9139873a4f3cef37826 Mon Sep 17 00:00:00 2001 From: Patrick Date: Wed, 27 Mar 2024 23:54:05 -0300 Subject: [PATCH] error handler middleware --- package-lock.json | 9 + package.json | 1 + src/controllers/currencyController.ts | 103 ++++------ src/index.ts | 4 +- src/middlewares/error.ts | 14 ++ src/middlewares/requestValidation.ts | 8 +- src/routes/currencyRoute.ts | 1 - src/services/currencyService.ts | 285 +++++++++++--------------- src/utils/apiError.ts | 13 ++ 9 files changed, 200 insertions(+), 238 deletions(-) create mode 100644 src/middlewares/error.ts create mode 100644 src/utils/apiError.ts diff --git a/package-lock.json b/package-lock.json index 174339011..a255055c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "dotenv": "^16.4.5", "env-var": "^7.4.1", "express": "^4.19.2", + "express-async-errors": "^3.1.1", "joi": "^17.12.2", "knex": "^3.1.0", "pg": "^8.11.3", @@ -686,6 +687,14 @@ "node": ">= 0.10.0" } }, + "node_modules/express-async-errors": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==", + "peerDependencies": { + "express": "^4.16.2" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", diff --git a/package.json b/package.json index df660ff0d..260bef262 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dotenv": "^16.4.5", "env-var": "^7.4.1", "express": "^4.19.2", + "express-async-errors": "^3.1.1", "joi": "^17.12.2", "knex": "^3.1.0", "pg": "^8.11.3", diff --git a/src/controllers/currencyController.ts b/src/controllers/currencyController.ts index 77a1c7916..13197f7fd 100644 --- a/src/controllers/currencyController.ts +++ b/src/controllers/currencyController.ts @@ -1,36 +1,25 @@ import { Request, Response } from "express"; import { currencyService } from "../services/currencyService"; import { Conversion, Currency } from "../types/currency"; +import { BadRequestError } from "../utils/apiError"; const listCurrencies = async (_: Request, res: Response) => { - try { - const currencies = await currencyService.getCurrencies(); - - return res.status(200).json(currencies); - } catch (error: any) { - return res.status(500).json({ message: error.message }); - } + const currencies = await currencyService.getCurrencies(); + return res.status(200).json(currencies); }; const getCurrency = async (req: Request, res: Response) => { const code = (req.params.code as string).toUpperCase(); - try { - const supportedCurrencies = - await currencyService.getSupportedCurrencies(); + const supportedCurrencies = await currencyService.getSupportedCurrencies(); - if (!supportedCurrencies.includes(code)) { - return res - .status(400) - .json({ message: "The currency is not supported" }); - } + if (!supportedCurrencies.includes(code)) { + throw new BadRequestError("The currency is not supported"); + } - const currency = await currencyService.getCurrency(code); + const currency = await currencyService.getCurrency(code); - return res.status(200).json(currency); - } catch (error: any) { - return res.status(500).json({ message: error.message }); - } + return res.status(200).json(currency); }; const convertCurrency = async (req: Request, res: Response) => { @@ -43,36 +32,29 @@ const convertCurrency = async (req: Request, res: Response) => { const uppercaseFrom = from.toUpperCase(); const uppercaseTo = to.toUpperCase(); - try { - const supportedCurrencies = - await currencyService.getSupportedCurrencies(); - - if (!supportedCurrencies.includes(uppercaseFrom)) { - return res - .status(400) - .json({ message: "The currency from is not supported" }); - } - - if (!supportedCurrencies.includes(uppercaseTo)) { - return res - .status(400) - .json({ message: "The currency to is not supported" }); - } - - const data: Conversion = { - from: uppercaseFrom, - to: uppercaseTo, - amount, - }; - - const conversion: Conversion = await currencyService.getConversion( - data + const supportedCurrencies = await currencyService.getSupportedCurrencies(); + + if (!supportedCurrencies.includes(uppercaseFrom)) { + throw new BadRequestError( + `The currency ${uppercaseFrom} is not supported` ); + } - return res.status(200).json(conversion); - } catch (error: any) { - return res.status(500).json({ message: error.message }); + if (!supportedCurrencies.includes(uppercaseTo)) { + throw new BadRequestError( + `The currency ${uppercaseTo} is not supported` + ); } + + const data: Conversion = { + from: uppercaseFrom, + to: uppercaseTo, + amount, + }; + + const conversion: Conversion = await currencyService.getConversion(data); + + return res.status(200).json(conversion); }; const createCurrency = async (req: Request, res: Response) => { @@ -84,13 +66,8 @@ const createCurrency = async (req: Request, res: Response) => { value, }; - try { - await currencyService.createCurrency(currency); - - return res.status(201).json(currency); - } catch (error: any) { - return res.status(500).json({ message: error.message }); - } + await currencyService.createCurrency(currency); + return res.status(201).json(currency); }; const updateCurrency = async (req: Request, res: Response) => { @@ -103,25 +80,15 @@ const updateCurrency = async (req: Request, res: Response) => { value, }; - try { - await currencyService.updateCurrency(codeParams, currency); - - return res.status(204).send(); - } catch (error: any) { - return res.status(500).json({ message: error.message }); - } + await currencyService.updateCurrency(codeParams, currency); + return res.status(204).send(); }; const deleteCurrency = async (req: Request, res: Response) => { const code = (req.params.code as string).toUpperCase(); - try { - await currencyService.deleteCurrency(code); - - return res.status(204).send(); - } catch (error: any) { - return res.status(500).json({ message: error.message }); - } + await currencyService.deleteCurrency(code); + return res.status(204).send(); }; export const currencyController = { diff --git a/src/index.ts b/src/index.ts index 199e1408e..ea49984db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,16 +1,18 @@ import "dotenv/config"; +import 'express-async-errors' import env from "./config/envConfig"; import express from "express"; import redis from "./database/redis"; import currencyRoutes from "./routes/currencyRoute"; +import errorMiddleware from "./middlewares/error"; redis.connect(); - const app = express(); app.use(express.json()); app.use(currencyRoutes); +app.use(errorMiddleware) app.listen(env.PORT, () => console.log(`Server running on port ${env.PORT}`)); diff --git a/src/middlewares/error.ts b/src/middlewares/error.ts new file mode 100644 index 000000000..e7e74cde5 --- /dev/null +++ b/src/middlewares/error.ts @@ -0,0 +1,14 @@ +import { Request, Response, NextFunction } from "express"; +import { ApiError } from "../utils/apiError"; + +export default function errorMiddleware( + error: Error & Partial, + req: Request, + res: Response, + next: NextFunction +) { + const statusCode = error.statusCode ?? 500; + const message = + statusCode === 500 ? "Internal server error" : error.message; + res.status(statusCode).json({ message }); +} diff --git a/src/middlewares/requestValidation.ts b/src/middlewares/requestValidation.ts index 5dedee76c..ca66d1158 100644 --- a/src/middlewares/requestValidation.ts +++ b/src/middlewares/requestValidation.ts @@ -1,5 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { ObjectSchema } from "joi"; +import { BadRequestError } from "../utils/apiError"; const requestValidation = (schema: ObjectSchema) => @@ -8,11 +9,8 @@ const requestValidation = const params = Object.keys(req.params).length; const query = Object.keys(req.query).length; if (req.method !== "GET" && !body && !params && !query) { - return res - .status(400) - .json({ message: "Please fill out all the fields" }); + throw new BadRequestError("Please fill out all the fields"); } - try { if (body) { await schema.validateAsync({ body: req.body }); @@ -26,7 +24,7 @@ const requestValidation = return next(); } catch (error: any) { - return res.status(400).json({ message: error.message }); + throw new BadRequestError(error.message); } }; diff --git a/src/routes/currencyRoute.ts b/src/routes/currencyRoute.ts index 3e8e7d873..8fb41b58c 100644 --- a/src/routes/currencyRoute.ts +++ b/src/routes/currencyRoute.ts @@ -2,7 +2,6 @@ import { Router } from "express"; import { currencyController } from "../controllers/currencyController"; import joi from "../middlewares/requestValidation"; import { schema } from "../models/joiCurrency"; -import redis from "../database/redis"; const route = Router(); diff --git a/src/services/currencyService.ts b/src/services/currencyService.ts index fdf5b52ee..f6ca79d27 100644 --- a/src/services/currencyService.ts +++ b/src/services/currencyService.ts @@ -3,246 +3,205 @@ import redis from "../database/redis"; import knex from "../database/postgres"; import { CurrencyFetch, Currency, Conversion } from "../types/currency"; import { utilsCurrency } from "../utils/utilsCurrency"; +import { BadRequestError } from "../utils/apiError"; const getSupportedCurrencies = async (): Promise => { - try { - const supportedCurrencies = await redis.get(`${env.KEY}-supported`); + const supportedCurrencies = await redis.get(`${env.KEY}-supported`); - if (supportedCurrencies) { - return JSON.parse(supportedCurrencies); - } - - const currencies = await knex("currency") - .select("code") - .then((data) => data.map((currency) => currency.code)); + if (supportedCurrencies) { + return JSON.parse(supportedCurrencies); + } - if (currencies.length) { - utilsCurrency.insertCurrencyOnRedis( - `${env.KEY}-supported`, - currencies - ); - return currencies; - } + const currencies = await knex("currency") + .select("code") + .then((data) => data.map((currency) => currency.code)); + if (currencies.length) { + utilsCurrency.insertCurrencyOnRedis(`${env.KEY}-supported`, currencies); return currencies; - } catch (error: any) { - throw new Error("Error fetching supported currencies"); } + + return currencies; }; const getCurrencies = async (): Promise => { - try { - const currenciesRedis = await redis.get(env.KEY); + const currenciesRedis = await redis.get(env.KEY); - if (currenciesRedis) { - return JSON.parse(currenciesRedis) as Currency[]; - } + if (currenciesRedis) { + return JSON.parse(currenciesRedis) as Currency[]; + } - const supportedCurrencies = await getSupportedCurrencies(); + const supportedCurrencies = await getSupportedCurrencies(); - const response: Response = await fetch( - "https://economia.awesomeapi.com.br/json/all" - ); - const responseData = await response.json(); + const response: Response = await fetch( + "https://economia.awesomeapi.com.br/json/all" + ); + const responseData = await response.json(); - if (!responseData) { - throw new Error("Error fetching currencies"); - } + if (!responseData) { + throw new BadRequestError("Error fetching currencies"); + } - const data = responseData as { [key: string]: CurrencyFetch }; + const data = responseData as { [key: string]: CurrencyFetch }; - const currencies = await utilsCurrency.processAndStoreCurrencies( - data, - supportedCurrencies - ); + const currencies = await utilsCurrency.processAndStoreCurrencies( + data, + supportedCurrencies + ); - await utilsCurrency.insertCurrencyOnRedis(env.KEY, currencies); + await utilsCurrency.insertCurrencyOnRedis(env.KEY, currencies); - return currencies; - } catch { - throw new Error("Error fetching currencies"); - } + return currencies; }; const getCurrency = async (code: string): Promise => { - try { - const currencyRedis = await redis.get(`${env.KEY}-${code}`); - - if (currencyRedis) { - return JSON.parse(currencyRedis) as Currency; - } + const currencyRedis = await redis.get(`${env.KEY}-${code}`); - const currencies: Currency[] = await getCurrencies(); + if (currencyRedis) { + return JSON.parse(currencyRedis) as Currency; + } - const currency = currencies.find((currency) => currency.code === code); + const currencies: Currency[] = await getCurrencies(); - if (!currency) { - throw new Error("Currency not found"); - } + const currency = currencies.find((currency) => currency.code === code); - return currency; - } catch { - throw new Error("Error fetching currency"); + if (!currency) { + throw new BadRequestError("Currency not found"); } + + return currency; }; const getConversion = async (data: Conversion): Promise => { const { from, to, amount } = data; - try { - const currencyFrom = await getCurrency(from); - const currencyTo = await getCurrency(to); + const currencyFrom = await getCurrency(from); + const currencyTo = await getCurrency(to); - const conversion = (currencyFrom.value / currencyTo.value) * amount; + const conversion = (currencyFrom.value / currencyTo.value) * amount; - const conversionInfo: Conversion = { - from, - to, - amount, - conversion: `${conversion} ${to}`, - }; + const conversionInfo: Conversion = { + from, + to, + amount, + conversion: `${conversion} ${to}`, + }; - return conversionInfo; - } catch { - throw new Error("Error fetching conversion"); - } + return conversionInfo; }; const createCurrency = async (currency: Currency): Promise => { - try { - const supportedCurrencies = await getSupportedCurrencies(); + const supportedCurrencies = await getSupportedCurrencies(); - if (supportedCurrencies.includes(currency.code)) { - throw new Error("Currency already exists"); - } + if (supportedCurrencies.includes(currency.code)) { + throw new BadRequestError("Currency already exists"); + } - supportedCurrencies.push(currency.code); + supportedCurrencies.push(currency.code); - await knex("currency").insert(currency); + await knex("currency").insert(currency); - const currencies = await redis.get(env.KEY); + const currencies = await redis.get(env.KEY); - if (currencies) { - const currenciesParsed: Currency[] = JSON.parse(currencies); + if (currencies) { + const currenciesParsed: Currency[] = JSON.parse(currencies); - currenciesParsed.push(currency); + currenciesParsed.push(currency); - await utilsCurrency.insertCurrencyOnRedis( - env.KEY, - currenciesParsed - ); - } + await utilsCurrency.insertCurrencyOnRedis(env.KEY, currenciesParsed); + } - await utilsCurrency.insertCurrencyOnRedis( - `${env.KEY}-${currency.code}`, - currency - ); + await utilsCurrency.insertCurrencyOnRedis( + `${env.KEY}-${currency.code}`, + currency + ); - await utilsCurrency.insertCurrencyOnRedis( - `${env.KEY}-supported`, - supportedCurrencies - ); - } catch { - throw new Error("Error creating currency"); - } + await utilsCurrency.insertCurrencyOnRedis( + `${env.KEY}-supported`, + supportedCurrencies + ); }; const updateCurrency = async ( codeParams: string, currency: Currency ): Promise => { - try { - const supportedCurrencies = - await currencyService.getSupportedCurrencies(); + const supportedCurrencies = await currencyService.getSupportedCurrencies(); - if (!supportedCurrencies.includes(codeParams)) { - throw new Error("Currency does not exist"); - } + if (!supportedCurrencies.includes(codeParams)) { + throw new BadRequestError("Currency does not exist"); + } - if (supportedCurrencies.includes(currency.code)) { - throw new Error("Currency already exists"); - } + if (supportedCurrencies.includes(currency.code)) { + throw new BadRequestError("Currency already exists"); + } - await knex("currency") - .update(currency) - .where({ code: codeParams }); + await knex("currency") + .update(currency) + .where({ code: codeParams }); - await utilsCurrency.insertCurrencyOnRedis( - `${env.KEY}-${currency.code}`, - currency - ); + await utilsCurrency.insertCurrencyOnRedis( + `${env.KEY}-${currency.code}`, + currency + ); - const currencies = await redis.get(env.KEY); + const currencies = await redis.get(env.KEY); - if (currencies) { - const currenciesParsed: Currency[] = JSON.parse(currencies); + if (currencies) { + const currenciesParsed: Currency[] = JSON.parse(currencies); - const index = currenciesParsed.findIndex( - (currencyRedis) => currencyRedis.code === currency.code - ); + const index = currenciesParsed.findIndex( + (currencyRedis) => currencyRedis.code === currency.code + ); - currenciesParsed[index] = currency; + currenciesParsed[index] = currency; - await utilsCurrency.insertCurrencyOnRedis( - env.KEY, - currenciesParsed - ); - } + await utilsCurrency.insertCurrencyOnRedis(env.KEY, currenciesParsed); + } - supportedCurrencies.push(currency.code); + supportedCurrencies.push(currency.code); - await utilsCurrency.insertCurrencyOnRedis( - `${env.KEY}-supported`, - supportedCurrencies - ); - } catch { - throw new Error("Error updating currency"); - } + await utilsCurrency.insertCurrencyOnRedis( + `${env.KEY}-supported`, + supportedCurrencies + ); }; const deleteCurrency = async (code: string): Promise => { - try { - const supportedCurrencies = - await currencyService.getSupportedCurrencies(); + const supportedCurrencies = await currencyService.getSupportedCurrencies(); - if (!supportedCurrencies.includes(code)) { - throw new Error("Currency does not exist"); - } + if (!supportedCurrencies.includes(code)) { + throw new BadRequestError("Currency does not exist"); + } - await knex("currency").delete().where({ code }); + await knex("currency").delete().where({ code }); - await redis.del(`${env.KEY}-${code}`); + await redis.del(`${env.KEY}-${code}`); - const currencies = await redis.get(env.KEY); + const currencies = await redis.get(env.KEY); - if (currencies) { - const currenciesParsed: Currency[] = JSON.parse(currencies); + if (currencies) { + const currenciesParsed: Currency[] = JSON.parse(currencies); - const index = currenciesParsed.findIndex( - (currency) => currency.code === code - ); + const index = currenciesParsed.findIndex( + (currency) => currency.code === code + ); - currenciesParsed.splice(index, 1); + currenciesParsed.splice(index, 1); - await utilsCurrency.insertCurrencyOnRedis( - env.KEY, - currenciesParsed - ); - } + await utilsCurrency.insertCurrencyOnRedis(env.KEY, currenciesParsed); + } - const supportedIndex = supportedCurrencies.findIndex( - (currency) => currency === code - ); + const supportedIndex = supportedCurrencies.findIndex( + (currency) => currency === code + ); - supportedCurrencies.splice(supportedIndex, 1); + supportedCurrencies.splice(supportedIndex, 1); - await utilsCurrency.insertCurrencyOnRedis( - `${env.KEY}-supported`, - supportedCurrencies - ); - } catch { - throw new Error("Error deleting currency"); - } + await utilsCurrency.insertCurrencyOnRedis( + `${env.KEY}-supported`, + supportedCurrencies + ); }; export const currencyService = { diff --git a/src/utils/apiError.ts b/src/utils/apiError.ts new file mode 100644 index 000000000..33c3c2719 --- /dev/null +++ b/src/utils/apiError.ts @@ -0,0 +1,13 @@ +export class ApiError extends Error { + public readonly statusCode: number; + constructor(message: string, statusCode: number) { + super(message); + this.statusCode = statusCode; + } +} + +export class BadRequestError extends ApiError { + constructor(message: string) { + super(message, 400); + } +} \ No newline at end of file