generated from azures04/Base-REST-API
Initial commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
const express = require("express")
|
||||
const hpp = require("hpp")
|
||||
const app = express()
|
||||
const cors = require("cors")
|
||||
const path = require("node:path")
|
||||
const utils = require("./modules/utils")
|
||||
const logger = require("./modules/logger")
|
||||
const helmet = require("helmet")
|
||||
const loader = require("./modules/loader")
|
||||
const DefaultError = require("./errors/DefaultError")
|
||||
const path2regex = require("path-to-regexp")
|
||||
|
||||
const routes = loader.getRecursiveFiles(path.join(__dirname, "routes"))
|
||||
const schemas = loader.getRecursiveFiles(path.join(__dirname, "schemas"))
|
||||
|
||||
const schemaRegistry = {}
|
||||
|
||||
app.use(hpp())
|
||||
app.use(helmet())
|
||||
app.use(cors({ origin: "*" }))
|
||||
|
||||
app.use(express.json())
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
|
||||
app.set("trust proxy", true)
|
||||
|
||||
logger.log("Initializing routes", ["WEB", "yellow"])
|
||||
|
||||
for (const schemaFile of schemas) {
|
||||
try {
|
||||
const schemaConfig = require(schemaFile)
|
||||
const routePath = loader.computeRoutePath(path.join(__dirname, "schemas"), schemaFile)
|
||||
schemaRegistry[routePath] = schemaConfig
|
||||
|
||||
logger.log(`${routePath.cyan.bold} schema loaded in memory`, ["WEB", "yellow"])
|
||||
} catch (error) {
|
||||
logger.error(error.toString(), ["WEB", "yellow"])
|
||||
}
|
||||
}
|
||||
|
||||
app.all(/.*/, (req, res, next) => {
|
||||
let currentPath = req.path
|
||||
if (currentPath.length > 1 && currentPath.endsWith("/")) {
|
||||
currentPath = currentPath.slice(0, -1)
|
||||
}
|
||||
|
||||
let schemaConfig = schemaRegistry[currentPath]
|
||||
let matchedParams = {}
|
||||
|
||||
if (!schemaConfig) {
|
||||
const registeredRoutes = Object.keys(schemaRegistry)
|
||||
for (const routePattern of registeredRoutes) {
|
||||
if (!routePattern.includes(":")) {
|
||||
continue
|
||||
}
|
||||
|
||||
const matcher = path2regex.match(routePattern, { decode: decodeURIComponent })
|
||||
const result = matcher(currentPath)
|
||||
|
||||
if (result) {
|
||||
schemaConfig = schemaRegistry[routePattern]
|
||||
matchedParams = result.params
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!schemaConfig || !schemaConfig[req.method]) {
|
||||
return next()
|
||||
}
|
||||
|
||||
req.params = { ...req.params, ...matchedParams }
|
||||
|
||||
const methodConfig = schemaConfig[req.method]
|
||||
const errorConfig = methodConfig.error || { status: 400, message: "Validation Error" }
|
||||
|
||||
if (methodConfig.headers) {
|
||||
const headerResult = methodConfig.headers.safeParse(req.headers)
|
||||
if (!headerResult.success) {
|
||||
return utils.sendValidationError(req, res, headerResult, "headers", currentPath, errorConfig)
|
||||
}
|
||||
}
|
||||
|
||||
let dataSchema = null
|
||||
let dataToValidate = null
|
||||
let validationType = "body"
|
||||
|
||||
if (req.method === "GET" || req.method === "DELETE") {
|
||||
dataSchema = methodConfig.query
|
||||
dataToValidate = req.query
|
||||
validationType = "query"
|
||||
} else {
|
||||
dataSchema = methodConfig.body
|
||||
dataToValidate = req.body
|
||||
}
|
||||
|
||||
if (dataSchema) {
|
||||
const result = dataSchema.safeParse(dataToValidate)
|
||||
if (!result.success) {
|
||||
return utils.sendValidationError(req, res, result, validationType, currentPath, errorConfig)
|
||||
}
|
||||
if (validationType === "query") {
|
||||
req.query = result.data
|
||||
} else {
|
||||
req.body = result.data
|
||||
}
|
||||
}
|
||||
|
||||
return next()
|
||||
})
|
||||
|
||||
for (const route of routes) {
|
||||
try {
|
||||
const router = require(route)
|
||||
const routePath = loader.computeRoutePath(path.join(__dirname, "routes"), route)
|
||||
if (router.stack) {
|
||||
for (const layer of router.stack) {
|
||||
if (layer.route && layer.route.methods) {
|
||||
const method = Object.keys(layer.route.methods).join(", ").toUpperCase()
|
||||
const subPath = routePath === "/" ? "" : routePath + layer.route.path
|
||||
logger.log(`${method.cyan} ${subPath.cyan.bold} route registered`, ["WEB", "yellow"])
|
||||
}
|
||||
}
|
||||
}
|
||||
app.use(routePath, router)
|
||||
} catch (error) {
|
||||
logger.error(error.toString(), ["WEB", "yellow"])
|
||||
}
|
||||
}
|
||||
|
||||
app.all(/.*/, (req, res, next) => {
|
||||
next(new DefaultError(404, `Can't find ${req.originalUrl} on this server!`, null, "NotFound"))
|
||||
})
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
const statusCode = err.code || 500
|
||||
|
||||
logger.error(err.message, ["API", "red"])
|
||||
|
||||
if (typeof err.serialize === "function") {
|
||||
return res.status(statusCode).json(err.serialize())
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
status: "error",
|
||||
message: "Internal Server Error"
|
||||
})
|
||||
})
|
||||
|
||||
app.listen(process.env.WEB_PORT || 3000, () => {
|
||||
logger.log(`Server listening at port : ${process.env.WEB_PORT || 3000}`, ["WEB", "yellow"])
|
||||
})
|
||||
Reference in New Issue
Block a user