From 76b6f05e929996f8395ede3dab0b5661a62cfd69 Mon Sep 17 00:00:00 2001 From: Kaushik Date: Fri, 7 Oct 2022 14:25:50 +0530 Subject: [PATCH] Boilerplates, base config, folder structure, utils, etc. --- .gitignore | 18 ++++++++++++ .sequelizerc | 6 ++++ README.md | 1 - boilerplates/controller.js | 19 ++++++++++++ boilerplates/route.js | 13 +++++++++ boilerplates/validator.js | 22 ++++++++++++++ config/config.js | 16 +++++++++++ models/index.js | 40 ++++++++++++++++++++++++++ typedefs.js | 15 ++++++++++ utils/logger.js | 59 ++++++++++++++++++++++++++++++++++++++ validators/index.js | 28 ++++++++++++++++++ 11 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .sequelizerc create mode 100644 boilerplates/controller.js create mode 100644 boilerplates/route.js create mode 100644 boilerplates/validator.js create mode 100644 config/config.js create mode 100644 models/index.js create mode 100644 typedefs.js create mode 100644 utils/logger.js create mode 100644 validators/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b358cc8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Dependency directories +node_modules/ + +# dotenv environment variables file +.env.development +.env.production +*.env + +# Data files +*/*.csv diff --git a/.sequelizerc b/.sequelizerc new file mode 100644 index 0000000..3794b85 --- /dev/null +++ b/.sequelizerc @@ -0,0 +1,6 @@ +require("dotenv-flow").config(); + +const path = require("path"); +module.exports = { + "config": path.resolve("config", "config.js") +}; \ No newline at end of file diff --git a/README.md b/README.md index 6ceb3a9..0eedb70 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ # Induction app back-end - diff --git a/boilerplates/controller.js b/boilerplates/controller.js new file mode 100644 index 0000000..9b43d5a --- /dev/null +++ b/boilerplates/controller.js @@ -0,0 +1,19 @@ +const typedefs = require("../typedefs"); +const logger = require("../utils/logger")(module); + +/** + * @param {typedefs.Req} req + * @param {typedefs.Res} res + */ +const __controller_func = async (req, res) => { + try { + + } catch (error) { + logger.error("Error", { error }); + return res.status(500).send({ message: "Server Error. Try again." }); + } +} + +module.exports = { + __controller_func +}; \ No newline at end of file diff --git a/boilerplates/route.js b/boilerplates/route.js new file mode 100644 index 0000000..a4ef0a5 --- /dev/null +++ b/boilerplates/route.js @@ -0,0 +1,13 @@ +const router = require("express").Router(); + +const { validate } = require("../validators"); + +router.get( + +); + +router.post( + +); + +module.exports = router; diff --git a/boilerplates/validator.js b/boilerplates/validator.js new file mode 100644 index 0000000..49d9ed6 --- /dev/null +++ b/boilerplates/validator.js @@ -0,0 +1,22 @@ +const { body, header, param, query } = require("express-validator"); + +const typedefs = require("../typedefs"); + +/** + * @param {typedefs.Req} req + * @param {typedefs.Res} res + * @param {typedefs.Next} next + */ +const __validator_func = async (req, res, next) => { + await body("field_name") + .notEmpty() + .withMessage("field_name not defined in body") + .run(req); + + next(); +} + +module.exports = { + __validator_func, +} + diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..45a5ca5 --- /dev/null +++ b/config/config.js @@ -0,0 +1,16 @@ +module.exports = { + "development": { + "username": process.env.DB_USERNAME, // local PostgreSQL DB username + "password": process.env.DB_PASSWORD, // local PostgreSQL DB password + "host": "127.0.0.1", // localhost + "database": process.env.DB_NAME, // local PostgreSQL DB name + "dialect": "postgres" + }, + "production": { + "use_env_variable": "DB_URL", // production database connection string + "dialect": "postgres", + "dialectOptions": { + "ssl": true, + }, + } +} diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..e1e4b5e --- /dev/null +++ b/models/index.js @@ -0,0 +1,40 @@ +"use strict"; +const fs = require("fs"); +const path = require("path"); +const Sequelize = require("sequelize"); + +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || "development"; +const config = require(__dirname + "/../config/config.js")[env]; +const db = {}; + +// Create new Sequelize instance +let sequelize; +if (config.use_env_variable) { + sequelize = new Sequelize(process.env[config.use_env_variable], config); +} else { + sequelize = new Sequelize(config.database, config.username, config.password, config); +} + +// Read model definitions from folder +fs + .readdirSync(__dirname) + .filter(file => { + return (file.indexOf(".") !== 0) && (file !== basename) && (file.slice(-3) === ".js"); + }) + .forEach(file => { + const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); + db[model.name] = model; + }); + +// Setup defined associations +Object.keys(db).forEach(modelName => { + if (db[modelName].associate) { + db[modelName].associate(db); + } +}); + +db.sequelize = sequelize; +db.Sequelize = Sequelize; + +module.exports = db; diff --git a/typedefs.js b/typedefs.js new file mode 100644 index 0000000..dad07ef --- /dev/null +++ b/typedefs.js @@ -0,0 +1,15 @@ +/** + * @typedef {import("module")} Module + * + * @typedef {import("express").Request} Req + * @typedef {import("express").Response} Res + * @typedef {import("express").NextFunction} Next + * + * @typedef {import("sequelize") Sequelize} + * @typedef {import("sequelize").Model Model} + * @typedef {import("sequelize").QueryInterface QueryInterface} + * + * @typedef {import("winston").Logger} Logger + */ + +exports.unused = {}; \ No newline at end of file diff --git a/utils/logger.js b/utils/logger.js new file mode 100644 index 0000000..0848582 --- /dev/null +++ b/utils/logger.js @@ -0,0 +1,59 @@ +const path = require("path"); + +const { createLogger, transports, config, format } = require("winston"); +const { combine, label, timestamp, printf } = format; + +const typedefs = require("../typedefs"); + +const getLabel = (callingModule) => { + const parts = callingModule.filename.split(path.sep); + return path.join(parts[parts.length - 2], parts.pop()); +}; + +const logMetaReplacer = (key, value) => { + if (key === "error") { + return value.name + ": " + value.message; + } + return value; +} + +const metaFormat = (meta) => { + if (Object.keys(meta).length > 0) + return "\n" + JSON.stringify(meta, logMetaReplacer) + "\n"; + return "\n"; +} + +const logFormat = printf(({ level, message, label, timestamp, ...meta }) => { + if (meta.error) { + for (const key in meta.error) { + if (typeof key !== "symbol" && key !== "message" && key !== "name") { + delete meta.error[key] + } + } + } + return `${timestamp} [${label}] ${level}: ${message}${metaFormat(meta)}`; +}); + +/** + * Creates a curried function, and call it with the module in use to get logs with filename + * @param {typedefs.Module} callingModule The module from which the logger is called + * @returns {typedefs.Logger} + */ +const logger = (callingModule) => { + return createLogger({ + levels: config.npm.levels, + format: combine( + label({ label: getLabel(callingModule) }), + timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + logFormat, + ), + transports: [ + process.env.NODE_ENV !== "production" ? + new transports.Console() : + new transports.File({ filename: __dirname + "/../logs/common.log" }), + new transports.File({ filename: __dirname + "/../logs/error.log", level: "error" }), + ] + }); +} + +module.exports = logger; diff --git a/validators/index.js b/validators/index.js new file mode 100644 index 0000000..4f6877e --- /dev/null +++ b/validators/index.js @@ -0,0 +1,28 @@ +const { validationResult } = require("express-validator"); + +const typedefs = require("../typedefs"); + +/** + * Middleware to call after running validators to finalize result + * @param {typedefs.Req} req + * @param {typedefs.Res} res + * @param {typedefs.Next} next + */ +const validate = (req, res, next) => { + const errors = validationResult(req); + if (errors.isEmpty()) { + return next(); + } + const extractedErrors = [] + errors.array().map(err => extractedErrors.push({ + [err.param]: err.msg + })); + + return res.status(400).json({ + message: extractedErrors, + }) +} + +module.exports = { + validate, +} \ No newline at end of file