├── vanilla-js ├── emails │ ├── verify-email │ │ ├── subject.njk │ │ └── html.njk │ └── forgot-password │ │ ├── subject.njk │ │ └── html.njk ├── .gitignore ├── middleware │ ├── async.js │ ├── error.js │ └── advancedResults.js ├── utils │ ├── errorResponse.js │ ├── sendEmailVerification.js │ └── sendEmail.js ├── routes.js ├── config │ ├── db.js │ └── .env.example ├── components │ ├── users │ │ ├── users.routes.js │ │ ├── users.controller.js │ │ └── user.model.js │ └── auth │ │ ├── auth.routes.js │ │ ├── auth.middleware.js │ │ └── auth.controller.js ├── package.json ├── seeder.js ├── app.js ├── _data │ └── users.json ├── README.md └── bin │ └── www ├── basic-typescript ├── src │ ├── emails │ │ ├── verify-email │ │ │ ├── subject.njk │ │ │ └── html.njk │ │ └── forgot-password │ │ │ ├── subject.njk │ │ │ └── html.njk │ ├── types │ │ ├── xss-clean.dt.ts │ │ └── custom.dt.ts │ ├── middleware │ │ ├── async.ts │ │ ├── error.ts │ │ └── advancedResults.ts │ ├── utils │ │ ├── errorResponse.ts │ │ ├── sendEmailVerification.ts │ │ └── sendEmail.ts │ ├── routes.ts │ ├── config │ │ └── db.ts │ ├── components │ │ ├── users │ │ │ ├── users.routes.ts │ │ │ ├── users.controller.ts │ │ │ └── user.model.ts │ │ └── auth │ │ │ ├── auth.routes.ts │ │ │ ├── auth.middleware.ts │ │ │ └── auth.controller.ts │ ├── seeder.ts │ ├── app.ts │ ├── _data │ │ └── users.json │ └── bin │ │ └── www.ts ├── .gitignore ├── .env.example ├── package.json ├── README.md └── tsconfig.json ├── advance-typescript-with-decorators ├── src │ ├── emails │ │ ├── verify-email │ │ │ ├── subject.njk │ │ │ └── html.njk │ │ └── forgot-password │ │ │ ├── subject.njk │ │ │ └── html.njk │ ├── types │ │ ├── xss-clean.dt.ts │ │ └── custom.dt.ts │ ├── decorators │ │ ├── index.ts │ │ ├── Methods.ts │ │ ├── MetadataKeys.ts │ │ ├── bodyValidator.ts │ │ ├── use.ts │ │ ├── routes.ts │ │ └── controller.ts │ ├── middleware │ │ ├── async.ts │ │ ├── error.ts │ │ └── advancedResults.ts │ ├── utils │ │ ├── errorResponse.ts │ │ ├── sendEmailVerification.ts │ │ └── sendEmail.ts │ ├── AppRouter.ts │ ├── config │ │ └── db.ts │ ├── seeder.ts │ ├── app.ts │ ├── _data │ │ └── users.json │ ├── components │ │ ├── auth │ │ │ ├── auth.middleware.ts │ │ │ └── auth.controller.ts │ │ └── users │ │ │ ├── users.controller.ts │ │ │ └── user.model.ts │ └── bin │ │ └── www.ts ├── .gitignore ├── .env.example ├── package.json ├── README.md └── tsconfig.json └── README.md /vanilla-js/emails/verify-email/subject.njk: -------------------------------------------------------------------------------- 1 | Email Verification -------------------------------------------------------------------------------- /vanilla-js/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | .env 3 | node_modules -------------------------------------------------------------------------------- /vanilla-js/emails/forgot-password/subject.njk: -------------------------------------------------------------------------------- 1 | Password reset request -------------------------------------------------------------------------------- /basic-typescript/src/emails/verify-email/subject.njk: -------------------------------------------------------------------------------- 1 | Email Verification -------------------------------------------------------------------------------- /basic-typescript/src/emails/forgot-password/subject.njk: -------------------------------------------------------------------------------- 1 | Password reset request -------------------------------------------------------------------------------- /basic-typescript/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | .env 3 | node_modules 4 | dist -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/emails/verify-email/subject.njk: -------------------------------------------------------------------------------- 1 | Email Verification -------------------------------------------------------------------------------- /advance-typescript-with-decorators/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | .env 3 | node_modules 4 | dist -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/emails/forgot-password/subject.njk: -------------------------------------------------------------------------------- 1 | Password reset request -------------------------------------------------------------------------------- /basic-typescript/src/types/xss-clean.dt.ts: -------------------------------------------------------------------------------- 1 | declare module 'xss-clean' { 2 | const value: Function 3 | 4 | export default value 5 | } 6 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/types/xss-clean.dt.ts: -------------------------------------------------------------------------------- 1 | declare module 'xss-clean' { 2 | const value: Function 3 | 4 | export default value 5 | } 6 | -------------------------------------------------------------------------------- /vanilla-js/middleware/async.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = fn => (req, res, next) => 2 | Promise.resolve(fn(req, res, next)).catch(next) 3 | 4 | module.exports = asyncHandler 5 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './controller' 2 | export * from './routes' 3 | export * from './use' 4 | export * from './bodyValidator' 5 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/Methods.ts: -------------------------------------------------------------------------------- 1 | export enum Methods { 2 | get = 'get', 3 | post = 'post', 4 | patch = 'patch', 5 | del = 'delete', 6 | put = 'put', 7 | } 8 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/MetadataKeys.ts: -------------------------------------------------------------------------------- 1 | export enum MetadataKeys { 2 | method = 'method', 3 | path = 'path', 4 | middleware = 'middleware', 5 | validator = 'validator', 6 | } 7 | -------------------------------------------------------------------------------- /basic-typescript/src/middleware/async.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | 3 | export default (fn: Function) => 4 | (req: Request, res: Response, next: NextFunction) => 5 | Promise.resolve(fn(req, res, next)).catch(next) 6 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/middleware/async.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | 3 | export default (fn: Function) => 4 | (req: Request, res: Response, next: NextFunction) => 5 | Promise.resolve(fn(req, res, next)).catch(next) 6 | -------------------------------------------------------------------------------- /basic-typescript/src/types/custom.dt.ts: -------------------------------------------------------------------------------- 1 | declare namespace Express { 2 | export interface Request { 3 | user: { 4 | [key: string]: string 5 | } 6 | } 7 | } 8 | 9 | declare namespace Express { 10 | export interface Response { 11 | advancedResults?: {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/types/custom.dt.ts: -------------------------------------------------------------------------------- 1 | declare namespace Express { 2 | export interface Request { 3 | user: { 4 | [key: string]: string 5 | } 6 | } 7 | } 8 | 9 | declare namespace Express { 10 | export interface Response { 11 | advancedResults?: {} 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vanilla-js/utils/errorResponse.js: -------------------------------------------------------------------------------- 1 | class ErrorResponse extends Error { 2 | constructor(message, statusCode, messageWithField = null) { 3 | super(message) 4 | this.statusCode = statusCode 5 | this.messageWithField = messageWithField 6 | } 7 | } 8 | 9 | module.exports = ErrorResponse 10 | -------------------------------------------------------------------------------- /basic-typescript/src/utils/errorResponse.ts: -------------------------------------------------------------------------------- 1 | export default class ErrorResponse extends Error { 2 | constructor( 3 | message: string | null, 4 | public statusCode: number | null = null, 5 | public messageWithField: null | object = null 6 | ) { 7 | super(message === null ? '' : message) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/utils/errorResponse.ts: -------------------------------------------------------------------------------- 1 | export default class ErrorResponse extends Error { 2 | constructor( 3 | message: string | null, 4 | public statusCode: number | null = null, 5 | public messageWithField: null | object = null 6 | ) { 7 | super(message === null ? '' : message) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/AppRouter.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | 3 | export class AppRouter { 4 | private static instance: express.Router 5 | 6 | static getInstance(): express.Router { 7 | if (!AppRouter.instance) { 8 | AppRouter.instance = express.Router() 9 | } 10 | 11 | return AppRouter.instance 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vanilla-js/routes.js: -------------------------------------------------------------------------------- 1 | const authRoutes = require('./components/auth/auth.routes') 2 | const userRoutes = require('./components/users/users.routes') 3 | 4 | const versionOne = (routeName) => `/api/v1/${routeName}` 5 | 6 | module.exports = (app) => { 7 | app.use(versionOne('auth'), authRoutes) 8 | app.use(versionOne('users'), userRoutes) 9 | } 10 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/bodyValidator.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata' 2 | import { MetadataKeys } from './MetadataKeys' 3 | 4 | export function bodyValidator(...keys: string[]) { 5 | return function (target: any, key: string, _: PropertyDescriptor) { 6 | Reflect.defineMetadata(MetadataKeys.validator, keys, target, key) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /basic-typescript/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { Express } from 'express' 2 | import authRoutes from './components/auth/auth.routes' 3 | import userRoutes from './components/users/users.routes' 4 | 5 | const versionOne = (routeName: string) => `/api/v1/${routeName}` 6 | 7 | export default (app: Express) => { 8 | app.use(versionOne('auth'), authRoutes) 9 | app.use(versionOne('users'), userRoutes) 10 | } 11 | -------------------------------------------------------------------------------- /basic-typescript/src/config/db.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const DBconnection = async () => { 4 | await mongoose 5 | .connect(process.env.MONGO_URI as string) 6 | .then((conn) => { 7 | console.log( 8 | `MongoDB Connected: ${conn.connection.host}`.cyan.underline.bold 9 | ) 10 | }) 11 | .catch((err) => { 12 | console.log(`For some reasons we couldn't connect to the DB`.red, err) 13 | }) 14 | } 15 | 16 | export default DBconnection 17 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/config/db.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const DBconnection = async () => { 4 | await mongoose 5 | .connect(process.env.MONGO_URI as string) 6 | .then((conn) => { 7 | console.log( 8 | `MongoDB Connected: ${conn.connection.host}`.cyan.underline.bold 9 | ) 10 | }) 11 | .catch((err) => { 12 | console.log(`For some reasons we couldn't connect to the DB`.red, err) 13 | }) 14 | } 15 | 16 | export default DBconnection 17 | -------------------------------------------------------------------------------- /vanilla-js/config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const DBconnection = async () => { 4 | const conn = await mongoose 5 | .connect(process.env.MONGO_URI, { 6 | useNewUrlParser: true, 7 | useUnifiedTopology: true, 8 | }) 9 | .catch((err) => { 10 | console.log(`For some reasons we couldn't connect to the DB`.red, err) 11 | }) 12 | 13 | console.log(`MongoDB Connected: ${conn.connection.host}`.cyan.underline.bold) 14 | } 15 | 16 | module.exports = DBconnection 17 | -------------------------------------------------------------------------------- /basic-typescript/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=3001 3 | 4 | MONGO_URI=mongodb://localhost/mongoose-boilerplate 5 | 6 | JWT_SECRET= 7 | JWT_EXPIRE=30d 8 | JWT_COOKIE_EXPIRE=30 9 | 10 | #In Minutes 11 | RESET_PASSWORD_EXPIRATION_TIME=10 12 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 13 | 14 | FILE_UPLOAD_PATH = ./public/uploads 15 | MAX_FILE_UPLOAD = 1000000 16 | 17 | SMTP_HOST=smtp.mailtrap.io 18 | SMTP_PORT=2525 19 | SMTP_EMAIL= 20 | SMTP_PASSWORD= 21 | FROM_EMAIL=noreply@boilerplate.com 22 | FROM_NAME=Boilerplate -------------------------------------------------------------------------------- /vanilla-js/config/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=3001 3 | 4 | MONGO_URI=mongodb://localhost/mongoose-boilerplate 5 | 6 | JWT_SECRET= 7 | JWT_EXPIRE=30d 8 | JWT_COOKIE_EXPIRE=30 9 | 10 | #In Minutes 11 | RESET_PASSWORD_EXPIRATION_TIME=10 12 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 13 | 14 | FILE_UPLOAD_PATH = ./public/uploads 15 | MAX_FILE_UPLOAD = 1000000 16 | 17 | SMTP_HOST=smtp.mailtrap.io 18 | SMTP_PORT=2525 19 | SMTP_EMAIL= 20 | SMTP_PASSWORD= 21 | FROM_EMAIL=noreply@boilerplate.com 22 | FROM_NAME=Boilerplate -------------------------------------------------------------------------------- /advance-typescript-with-decorators/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=3001 3 | 4 | MONGO_URI=mongodb://localhost/mongoose-boilerplate 5 | 6 | JWT_SECRET= 7 | JWT_EXPIRE=30d 8 | JWT_COOKIE_EXPIRE=30 9 | 10 | #In Minutes 11 | RESET_PASSWORD_EXPIRATION_TIME=10 12 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 13 | 14 | FILE_UPLOAD_PATH = ./public/uploads 15 | MAX_FILE_UPLOAD = 1000000 16 | 17 | SMTP_HOST=smtp.mailtrap.io 18 | SMTP_PORT=2525 19 | SMTP_EMAIL= 20 | SMTP_PASSWORD= 21 | FROM_EMAIL=noreply@boilerplate.com 22 | FROM_NAME=Boilerplate -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/use.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from 'express' 2 | import 'reflect-metadata' 3 | import { MetadataKeys } from './MetadataKeys' 4 | 5 | export function use(middleware: RequestHandler) { 6 | return function (target: any, key: string, _: PropertyDescriptor) { 7 | const middlewares = 8 | Reflect.getMetadata(MetadataKeys.middleware, target, key) || [] 9 | 10 | Reflect.defineMetadata( 11 | MetadataKeys.middleware, 12 | [...middlewares, middleware], 13 | target, 14 | key 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /basic-typescript/src/components/users/users.routes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { 3 | getUsers, 4 | getUser, 5 | createUser, 6 | updateUser, 7 | deleteUser, 8 | } from './users.controller' 9 | 10 | import User from './user.model' 11 | 12 | const router = Router({ mergeParams: true }) 13 | 14 | import advancedResults from '../../middleware/advancedResults' 15 | import { protect, authorize } from '../auth/auth.middleware' 16 | 17 | router.use(protect) 18 | router.use(authorize('admin')) 19 | 20 | router.route('/').get(advancedResults(User), getUsers).post(createUser) 21 | 22 | router.route('/:id').get(getUser).put(updateUser).delete(deleteUser) 23 | 24 | export default router 25 | -------------------------------------------------------------------------------- /vanilla-js/emails/verify-email/html.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |Hello {{name}}
14 | 15 |You registered an account on express boilerplate, before being able to use your account you need to verify that this is your email address by clicking here: Verify Email
16 | 17 |The link below will expire in 10mins
18 | 19 |Kind Regards, express boilerplate
20 | 21 | 22 | -------------------------------------------------------------------------------- /basic-typescript/src/emails/verify-email/html.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |Hello {{name}}
14 | 15 |You registered an account on express boilerplate, before being able to use your account you need to verify that this is your email address by clicking here: Verify Email
16 | 17 |The link below will expire in 10mins
18 | 19 |Kind Regards, express boilerplate
20 | 21 | 22 | -------------------------------------------------------------------------------- /vanilla-js/components/users/users.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { 3 | getUsers, 4 | getUser, 5 | createUser, 6 | updateUser, 7 | deleteUser, 8 | } = require('./users.controller') 9 | 10 | const User = require('./user.model') 11 | 12 | const router = express.Router({ mergeParams: true }) 13 | 14 | const advancedResults = require('../../middleware/advancedResults') 15 | const { protect, authorize } = require('../auth/auth.middleware') 16 | 17 | router.use(protect) 18 | router.use(authorize('admin')) 19 | 20 | router.route('/').get(advancedResults(User), getUsers).post(createUser) 21 | 22 | router.route('/:id').get(getUser).put(updateUser).delete(deleteUser) 23 | 24 | module.exports = router 25 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/emails/verify-email/html.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |Hello {{name}}
14 | 15 |You registered an account on express boilerplate, before being able to use your account you need to verify that this is your email address by clicking here: Verify Email
16 | 17 |The link below will expire in 10mins
18 | 19 |Kind Regards, express boilerplate
20 | 21 | 22 | -------------------------------------------------------------------------------- /vanilla-js/emails/forgot-password/html.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |Trouble signing in?
14 | 15 |Resetting your password is easy.
16 | 17 | Just press the button below and follow the instructions. We’ll have you up and running in no time. The link below will expire in 10mins 18 | Reset Password 19 | 20 |If you did not make this request then please ignore this email.
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /basic-typescript/src/emails/forgot-password/html.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |Trouble signing in?
14 | 15 |Resetting your password is easy.
16 | 17 | Just press the button below and follow the instructions. We’ll have you up and running in no time. The link below will expire in 10mins 18 | Reset Password 19 | 20 |If you did not make this request then please ignore this email.
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/emails/forgot-password/html.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |Trouble signing in?
14 | 15 |Resetting your password is easy.
16 | 17 | Just press the button below and follow the instructions. We’ll have you up and running in no time. The link below will expire in 10mins 18 | Reset Password 19 | 20 |If you did not make this request then please ignore this email.
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/routes.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from 'express' 2 | import 'reflect-metadata' 3 | import { MetadataKeys } from './MetadataKeys' 4 | import { Methods } from './Methods' 5 | 6 | interface RouteHandlerDescriptor extends PropertyDescriptor { 7 | value?: RequestHandler 8 | } 9 | 10 | function routeBinder(method: string) { 11 | return function (path: string) { 12 | return function (target: any, key: string, _: RouteHandlerDescriptor) { 13 | Reflect.defineMetadata(MetadataKeys.path, path, target, key) 14 | Reflect.defineMetadata(MetadataKeys.method, method, target, key) 15 | } 16 | } 17 | } 18 | 19 | export const get = routeBinder(Methods.get) 20 | export const put = routeBinder(Methods.put) 21 | export const post = routeBinder(Methods.post) 22 | export const del = routeBinder(Methods.del) 23 | export const patch = routeBinder(Methods.patch) 24 | -------------------------------------------------------------------------------- /basic-typescript/src/components/auth/auth.routes.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | const router = express.Router() 3 | 4 | import { 5 | register, 6 | login, 7 | logout, 8 | getMe, 9 | forgotPassword, 10 | resetPassword, 11 | updateDetails, 12 | updatePassword, 13 | emailVerification, 14 | postSendEmailVerification, 15 | } from './auth.controller' 16 | 17 | import { protect } from '../auth/auth.middleware' 18 | 19 | router.post('/register', register) 20 | router.post('/login', login) 21 | router.post('/logout', logout) 22 | router.post('/me', protect, getMe) 23 | router.put('/updatedetails', protect, updateDetails) 24 | router.put('/updatepassword', protect, updatePassword) 25 | router.post('/forgotpassword', forgotPassword) 26 | router.put('/resetpassword/:resettoken', resetPassword) 27 | router.put('/emailverification/:resettoken', emailVerification) 28 | router.post('/sendemailverification', postSendEmailVerification) 29 | 30 | export default router 31 | -------------------------------------------------------------------------------- /vanilla-js/components/auth/auth.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | const { 5 | register, 6 | login, 7 | logout, 8 | getMe, 9 | forgotPassword, 10 | resetPassword, 11 | updateDetails, 12 | updatePassword, 13 | emailVerification, 14 | sendEmailVerification, 15 | } = require('./auth.controller') 16 | 17 | const { protect } = require('../auth/auth.middleware') 18 | 19 | router.post('/register', register) 20 | router.post('/login', login) 21 | router.post('/logout', logout) 22 | router.post('/me', protect, getMe) 23 | router.put('/updatedetails', protect, updateDetails) 24 | router.put('/updatepassword', protect, updatePassword) 25 | router.post('/forgotpassword', forgotPassword) 26 | router.put('/resetpassword/:resettoken', resetPassword) 27 | router.put('/emailverification/:resettoken', emailVerification) 28 | router.post('/sendemailverification', sendEmailVerification) 29 | 30 | module.exports = router 31 | -------------------------------------------------------------------------------- /vanilla-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mongoose-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node ./bin/www", 8 | "dev": "nodemon ./bin/www" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "dependencies": { 14 | "bcryptjs": "^2.4.3", 15 | "colors": "^1.4.0", 16 | "cookie-parser": "^1.4.6", 17 | "cors": "^2.8.5", 18 | "debug": "^4.3.4", 19 | "dotenv": "^16.0.2", 20 | "email-templates": "^10.0.1", 21 | "express": "^4.18.1", 22 | "express-mongo-sanitize": "^2.2.0", 23 | "express-rate-limit": "^6.6.0", 24 | "helmet": "^6.0.0", 25 | "hpp": "^0.2.3", 26 | "jsonwebtoken": "^8.5.1", 27 | "mongoose": "^6.6.1", 28 | "mongoose-unique-validator": "^3.1.0", 29 | "morgan": "^1.10.0", 30 | "nodemailer": "^6.7.8", 31 | "nunjucks": "^3.2.3", 32 | "xss-clean": "^0.1.1" 33 | }, 34 | "devDependencies": { 35 | "nodemon": "^2.0.19" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vanilla-js/utils/sendEmailVerification.js: -------------------------------------------------------------------------------- 1 | const sendEmail = require('./sendEmail') 2 | // const errorResponse = require('./errorResponse') 3 | 4 | async function sendEmailVerification(user, req) { 5 | const resetToken = user.getEmailVerificationToken() 6 | 7 | await user.save({ validateBeforeSave: false }) 8 | 9 | try { 10 | await sendEmail({ 11 | template: 'verify-email', 12 | email: user.email, 13 | locals: { 14 | name: user.name, 15 | link: `${req.protocol}://${req.get( 16 | 'host' 17 | )}/api/v1/auth/verify-email/${resetToken}`, 18 | }, 19 | }) 20 | 21 | // res.status(200).json({ success: true, data: 'Email sent' }) 22 | return true 23 | } catch (err) { 24 | console.log(err) 25 | user.isEmailVerified = false 26 | user.emailVerificationToken = undefined 27 | user.emailVerificationExpire = undefined 28 | 29 | await user.save({ validateBeforeSave: false }) 30 | return false 31 | // return next(new ErrorResponse('Email could not be sent', 500)) 32 | } 33 | } 34 | 35 | module.exports = sendEmailVerification 36 | -------------------------------------------------------------------------------- /basic-typescript/src/utils/sendEmailVerification.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express' 2 | 3 | import { IUser } from '../components/users/user.model' 4 | 5 | import sendEmail from './sendEmail' 6 | 7 | export default async function sendEmailVerification(user: IUser, req: Request) { 8 | const resetToken = user.getEmailVerificationToken() 9 | 10 | await (user as any).save({ validateBeforeSave: false }) 11 | 12 | try { 13 | await sendEmail({ 14 | template: 'verify-email', 15 | email: user.email, 16 | locals: { 17 | name: user.name, 18 | link: `${req.protocol}://${req.get( 19 | 'host' 20 | )}/api/v1/auth/verify-email/${resetToken}`, 21 | }, 22 | }) 23 | 24 | // res.status(200).json({ success: true, data: 'Email sent' }) 25 | return true 26 | } catch (err) { 27 | console.log(err) 28 | user.isEmailVerified = false 29 | user.emailVerificationToken = undefined 30 | user.emailVerificationExpire = undefined 31 | 32 | await (user as any).save({ validateBeforeSave: false }) 33 | return false 34 | // return next(new ErrorResponse('Email could not be sent', 500)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/utils/sendEmailVerification.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express' 2 | 3 | import { IUser } from '../components/users/user.model' 4 | 5 | import sendEmail from './sendEmail' 6 | 7 | export default async function sendEmailVerification(user: IUser, req: Request) { 8 | const resetToken = user.getEmailVerificationToken() 9 | 10 | await (user as any).save({ validateBeforeSave: false }) 11 | 12 | try { 13 | await sendEmail({ 14 | template: 'verify-email', 15 | email: user.email, 16 | locals: { 17 | name: user.name, 18 | link: `${req.protocol}://${req.get( 19 | 'host' 20 | )}/api/v1/auth/verify-email/${resetToken}`, 21 | }, 22 | }) 23 | 24 | // res.status(200).json({ success: true, data: 'Email sent' }) 25 | return true 26 | } catch (err) { 27 | console.log(err) 28 | user.isEmailVerified = false 29 | user.emailVerificationToken = undefined 30 | user.emailVerificationExpire = undefined 31 | 32 | await (user as any).save({ validateBeforeSave: false }) 33 | return false 34 | // return next(new ErrorResponse('Email could not be sent', 500)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /vanilla-js/seeder.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const mongoose = require('mongoose') 3 | const colors = require('colors') 4 | const dotenv = require('dotenv') 5 | 6 | // Load env vars 7 | dotenv.config({ path: './config/.env' }) 8 | 9 | const User = require('./components/users/user.model') 10 | 11 | mongoose.connect(process.env.MONGO_URI, { 12 | useNewUrlParser: true, 13 | useUnifiedTopology: true, 14 | }) 15 | 16 | const users = JSON.parse( 17 | fs.readFileSync(`${__dirname}/_data/users.json`, 'utf-8') 18 | ) 19 | 20 | const importData = async () => { 21 | try { 22 | await User.create(users) 23 | 24 | console.log('Data Imported...'.green.inverse) 25 | process.exit() 26 | } catch (err) { 27 | console.error(err) 28 | } 29 | } 30 | 31 | const deleteData = async () => { 32 | try { 33 | await User.deleteMany() 34 | 35 | console.log('Data Destroyed...'.red.inverse) 36 | process.exit() 37 | } catch (err) { 38 | console.error(err) 39 | } 40 | } 41 | 42 | if (process.argv[2] === '-i') { 43 | // node seeder -i 44 | importData() 45 | } else if (process.argv[2] === '-d') { 46 | // node seeder -d 47 | deleteData() 48 | } 49 | -------------------------------------------------------------------------------- /basic-typescript/src/seeder.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const mongoose = require('mongoose') 3 | const colors = require('colors') 4 | const dotenv = require('dotenv') 5 | 6 | // Load env vars 7 | dotenv.config({ path: './config/.env' }) 8 | 9 | const User = require('./components/users/user.model') 10 | 11 | mongoose.connect(process.env.MONGO_URI, { 12 | useNewUrlParser: true, 13 | useUnifiedTopology: true, 14 | }) 15 | 16 | const users = JSON.parse( 17 | fs.readFileSync(`${__dirname}/_data/users.json`, 'utf-8') 18 | ) 19 | 20 | const importData = async () => { 21 | try { 22 | await User.create(users) 23 | 24 | console.log('Data Imported...'.green.inverse) 25 | process.exit() 26 | } catch (err) { 27 | console.error(err) 28 | } 29 | } 30 | 31 | const deleteData = async () => { 32 | try { 33 | await User.deleteMany() 34 | 35 | console.log('Data Destroyed...'.red.inverse) 36 | process.exit() 37 | } catch (err) { 38 | console.error(err) 39 | } 40 | } 41 | 42 | if (process.argv[2] === '-i') { 43 | // node seeder -i 44 | importData() 45 | } else if (process.argv[2] === '-d') { 46 | // node seeder -d 47 | deleteData() 48 | } 49 | -------------------------------------------------------------------------------- /vanilla-js/utils/sendEmail.js: -------------------------------------------------------------------------------- 1 | const { resolve, dirname } = require('path') 2 | 3 | const nodemailer = require('nodemailer') 4 | const Email = require('email-templates') 5 | 6 | const sendEmail = async ({ email, locals, template }) => { 7 | console.log('sendemail', email) 8 | const emailObj = new Email({ 9 | message: { 10 | from: { name: process.env.FROM_NAME, address: process.env.FROM_EMAIL }, 11 | }, 12 | // uncomment below to send emails in development/test env: 13 | send: true, 14 | transport: nodemailer.createTransport({ 15 | host: process.env.SMTP_HOST, 16 | port: process.env.SMTP_PORT, 17 | auth: { 18 | user: process.env.SMTP_EMAIL, 19 | pass: process.env.SMTP_PASSWORD, 20 | }, 21 | }), 22 | views: { 23 | root: resolve(dirname(__dirname) + '/emails/'), 24 | options: { 25 | extension: 'njk', 26 | }, 27 | }, 28 | // preview: false, 29 | }) 30 | 31 | const mail = await emailObj.send({ 32 | template, 33 | message: { 34 | to: email, 35 | }, 36 | locals: { 37 | ...locals, 38 | }, 39 | }) 40 | console.log('Message sent: %s', mail.messageId) 41 | } 42 | 43 | module.exports = sendEmail 44 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/seeder.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const mongoose = require('mongoose') 3 | const colors = require('colors') 4 | const dotenv = require('dotenv') 5 | 6 | // Load env vars 7 | dotenv.config({ path: './config/.env' }) 8 | 9 | const User = require('./components/users/user.model') 10 | 11 | mongoose.connect(process.env.MONGO_URI, { 12 | useNewUrlParser: true, 13 | useUnifiedTopology: true, 14 | }) 15 | 16 | const users = JSON.parse( 17 | fs.readFileSync(`${__dirname}/_data/users.json`, 'utf-8') 18 | ) 19 | 20 | const importData = async () => { 21 | try { 22 | await User.create(users) 23 | 24 | console.log('Data Imported...'.green.inverse) 25 | process.exit() 26 | } catch (err) { 27 | console.error(err) 28 | } 29 | } 30 | 31 | const deleteData = async () => { 32 | try { 33 | await User.deleteMany() 34 | 35 | console.log('Data Destroyed...'.red.inverse) 36 | process.exit() 37 | } catch (err) { 38 | console.error(err) 39 | } 40 | } 41 | 42 | if (process.argv[2] === '-i') { 43 | // node seeder -i 44 | importData() 45 | } else if (process.argv[2] === '-d') { 46 | // node seeder -d 47 | deleteData() 48 | } 49 | -------------------------------------------------------------------------------- /basic-typescript/src/utils/sendEmail.ts: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from 'path' 2 | 3 | import nodemailer from 'nodemailer' 4 | import Email from 'email-templates' 5 | 6 | export default async (data: { 7 | email: string 8 | locals: object 9 | template: string 10 | }) => { 11 | const { email, locals, template } = data 12 | console.log('sendemail', email) 13 | 14 | const emailObj = new Email({ 15 | message: { 16 | from: { name: process.env.FROM_NAME!, address: process.env.FROM_EMAIL! }, 17 | }, 18 | // uncomment below to send emails in development/test env: 19 | send: true, 20 | transport: nodemailer.createTransport({ 21 | host: process.env.SMTP_HOST, 22 | port: +process.env.SMTP_PORT!, 23 | auth: { 24 | user: process.env.SMTP_EMAIL, 25 | pass: process.env.SMTP_PASSWORD, 26 | }, 27 | }), 28 | views: { 29 | root: resolve(dirname(__dirname) + '/emails/'), 30 | options: { 31 | extension: 'njk', 32 | }, 33 | }, 34 | // preview: false, 35 | }) 36 | 37 | const mail = await emailObj.send({ 38 | template, 39 | message: { 40 | to: email, 41 | }, 42 | locals: { 43 | ...locals, 44 | }, 45 | }) 46 | console.log('Message sent: %s', mail.messageId) 47 | } 48 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/utils/sendEmail.ts: -------------------------------------------------------------------------------- 1 | import { resolve, dirname } from 'path' 2 | 3 | import nodemailer from 'nodemailer' 4 | import Email from 'email-templates' 5 | 6 | export default async (data: { 7 | email: string 8 | locals: object 9 | template: string 10 | }) => { 11 | const { email, locals, template } = data 12 | console.log('sendemail', email) 13 | 14 | const emailObj = new Email({ 15 | message: { 16 | from: { name: process.env.FROM_NAME!, address: process.env.FROM_EMAIL! }, 17 | }, 18 | // uncomment below to send emails in development/test env: 19 | send: true, 20 | transport: nodemailer.createTransport({ 21 | host: process.env.SMTP_HOST, 22 | port: +process.env.SMTP_PORT!, 23 | auth: { 24 | user: process.env.SMTP_EMAIL, 25 | pass: process.env.SMTP_PASSWORD, 26 | }, 27 | }), 28 | views: { 29 | root: resolve(dirname(__dirname) + '/emails/'), 30 | options: { 31 | extension: 'njk', 32 | }, 33 | }, 34 | // preview: false, 35 | }) 36 | 37 | const mail = await emailObj.send({ 38 | template, 39 | message: { 40 | to: email, 41 | }, 42 | locals: { 43 | ...locals, 44 | }, 45 | }) 46 | console.log('Message sent: %s', mail.messageId) 47 | } 48 | -------------------------------------------------------------------------------- /basic-typescript/src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import * as dotenv from 'dotenv' 3 | import 'colors' 4 | import morgan from 'morgan' 5 | import cookieParser from 'cookie-parser' 6 | import mongoSanitize from 'express-mongo-sanitize' 7 | import helmet from 'helmet' 8 | import xss from 'xss-clean' 9 | import rateLimit from 'express-rate-limit' 10 | import hpp from 'hpp' 11 | import cors from 'cors' 12 | 13 | import errorHandler from './middleware/error' 14 | 15 | dotenv.config() 16 | 17 | import DBConnection from './config/db' 18 | 19 | DBConnection() 20 | 21 | const app = express() 22 | 23 | app.use(express.json()) 24 | 25 | app.use(cookieParser()) 26 | 27 | if (process.env.NODE_ENV === 'development') { 28 | app.use(morgan('dev')) 29 | } 30 | 31 | // Sanitize data 32 | app.use(mongoSanitize()) 33 | 34 | // Set security headers 35 | app.use(helmet()) 36 | 37 | // Prevent XSS attacks 38 | app.use(xss()) 39 | 40 | // Enable CORS 41 | app.use(cors()) 42 | 43 | // Rate limiting 44 | const limiter = rateLimit({ 45 | windowMs: 10 * 60 * 1000, // 10 mins 46 | max: 100, // 100 request per 10 mins 47 | }) 48 | 49 | app.use(limiter) 50 | 51 | // Prevent http param pollution 52 | app.use(hpp()) 53 | 54 | import routes from './routes' 55 | 56 | routes(app) 57 | 58 | app.use(errorHandler) 59 | 60 | export default app 61 | -------------------------------------------------------------------------------- /vanilla-js/middleware/error.js: -------------------------------------------------------------------------------- 1 | const ErrorResponse = require('../utils/errorResponse') 2 | 3 | const errorHandler = (err, req, res, next) => { 4 | let error = { 5 | ...err 6 | } 7 | 8 | error.message = err.message 9 | 10 | // console.log(err.stack.red); 11 | console.log(err) 12 | 13 | // Mongoose bad ObjectId 14 | if (err.name === 'CastError') { 15 | // const message = `Resource not found with id of ${err.value}`; 16 | const message = `Resource not found` 17 | error = new ErrorResponse(message, 404) 18 | } 19 | // console.log(err.name); 20 | 21 | // Mongoose duplicate key 22 | if (err.code === 11000) { 23 | const message = 'Duplicate field value entered' 24 | console.log(err) 25 | error = new ErrorResponse(message, 400) 26 | } 27 | 28 | // Mongoose validation error 29 | if (err.name === 'ValidationError') { 30 | const message = [] 31 | Object.values(err.errors).forEach(errr => { 32 | message.push({ 33 | field: errr.properties.path, 34 | message: errr.message 35 | }) 36 | }) 37 | error = new ErrorResponse(null, 400, message) 38 | } 39 | 40 | res.status(error.statusCode || 500).json({ 41 | success: false, 42 | error: error.messageWithField || error.message || 'Server Error' 43 | }) 44 | } 45 | 46 | module.exports = errorHandler 47 | -------------------------------------------------------------------------------- /vanilla-js/components/auth/auth.middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | const asyncHandler = require('../../middleware/async') 3 | const ErrorResponse = require('../../utils/errorResponse') 4 | const User = require('../users/user.model') 5 | 6 | exports.protect = asyncHandler(async (req, res, next) => { 7 | let token 8 | 9 | if ( 10 | req.headers.authorization && 11 | req.headers.authorization.startsWith('Bearer') 12 | ) { 13 | token = req.headers.authorization.split(' ')[1] 14 | } 15 | // Set token from cookie 16 | // else if (req.cookies.token) { 17 | // token = req.cookies.token 18 | // } 19 | 20 | if (!token) { 21 | return next(new ErrorResponse('Not authorized to access this route', 401)) 22 | } 23 | 24 | try { 25 | // Verify token 26 | const decoded = jwt.verify(token, process.env.JWT_SECRET) 27 | 28 | req.user = await User.findById(decoded.id) 29 | next() 30 | } catch (err) { 31 | return next(new ErrorResponse('Not authorized to access this route', 401)) 32 | } 33 | }) 34 | 35 | // Grant access to specific roles 36 | exports.authorize = (...roles) => { 37 | return (req, res, next) => { 38 | if (!roles.includes(req.user.role)) { 39 | return next( 40 | new ErrorResponse( 41 | `User role ${req.user.role} is not authorized to access this route`, 42 | 403 43 | ) 44 | ) 45 | } 46 | next() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /vanilla-js/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const dotenv = require('dotenv') 3 | const colors = require('colors') 4 | const morgan = require('morgan') 5 | const cookieParser = require('cookie-parser') 6 | const mongoSanitize = require('express-mongo-sanitize') 7 | const helmet = require('helmet') 8 | const xss = require('xss-clean') 9 | const rateLimit = require('express-rate-limit') 10 | const hpp = require('hpp') 11 | const cors = require('cors') 12 | 13 | const errorHandler = require('./middleware/error') 14 | 15 | const DBConnection = require('./config/db') 16 | 17 | dotenv.config({ path: './config/.env' }) 18 | 19 | DBConnection() 20 | 21 | const app = express() 22 | 23 | app.use(express.json()) 24 | 25 | app.use(cookieParser()) 26 | 27 | if (process.env.NODE_ENV === 'development') { 28 | app.use(morgan('dev')) 29 | } 30 | 31 | // Sanitize data 32 | app.use(mongoSanitize()) 33 | 34 | // Set security headers 35 | app.use(helmet()) 36 | 37 | // Prevent XSS attacks 38 | app.use(xss()) 39 | 40 | // Enable CORS 41 | app.use(cors()) 42 | 43 | // Rate limiting 44 | const limiter = rateLimit({ 45 | windowMs: 10 * 60 * 1000, // 10 mins 46 | max: 100, // 100 request per 10 mins 47 | }) 48 | 49 | app.use(limiter) 50 | 51 | // Prevent http param pollution 52 | app.use(hpp()) 53 | 54 | require('./routes')(app) 55 | 56 | app.use(errorHandler) 57 | 58 | app.use(errorHandler) 59 | 60 | module.exports = app 61 | -------------------------------------------------------------------------------- /basic-typescript/src/middleware/error.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler, NextFunction, Request } from 'express' 2 | import ErrorResponse from '../utils/errorResponse' 3 | 4 | const errorHandler: ErrorRequestHandler = ( 5 | err, 6 | _: Request, 7 | res, 8 | _1: NextFunction 9 | ) => { 10 | let error = { 11 | ...err, 12 | } 13 | 14 | error.message = err.message 15 | 16 | // console.log(err.stack.red); 17 | console.log(err) 18 | 19 | // Mongoose bad ObjectId 20 | if (err.name === 'CastError') { 21 | // const message = `Resource not found with id of ${err.value}`; 22 | const message = `Resource not found` 23 | error = new ErrorResponse(message, 404) 24 | } 25 | // console.log(err.name); 26 | 27 | // Mongoose duplicate key 28 | if (err.code === 11000) { 29 | const message = 'Duplicate field value entered' 30 | console.log(err) 31 | error = new ErrorResponse(message, 400) 32 | } 33 | 34 | // Mongoose validation error 35 | if (err.name === 'ValidationError') { 36 | const message: object[] = [] 37 | Object.values(err.errors).forEach((errr: any) => { 38 | message.push({ 39 | field: errr.properties.path, 40 | message: errr.message, 41 | }) 42 | }) 43 | error = new ErrorResponse(null, 400, message) 44 | } 45 | 46 | res.status(error.statusCode || 500).json({ 47 | success: false, 48 | error: error.messageWithField || error.message || 'Server Error', 49 | }) 50 | } 51 | 52 | export default errorHandler 53 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/middleware/error.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler, NextFunction, Request } from 'express' 2 | import ErrorResponse from '../utils/errorResponse' 3 | 4 | const errorHandler: ErrorRequestHandler = ( 5 | err, 6 | _: Request, 7 | res, 8 | _1: NextFunction 9 | ) => { 10 | let error = { 11 | ...err, 12 | } 13 | 14 | error.message = err.message 15 | 16 | // console.log(err.stack.red); 17 | console.log(err) 18 | 19 | // Mongoose bad ObjectId 20 | if (err.name === 'CastError') { 21 | // const message = `Resource not found with id of ${err.value}`; 22 | const message = `Resource not found` 23 | error = new ErrorResponse(message, 404) 24 | } 25 | // console.log(err.name); 26 | 27 | // Mongoose duplicate key 28 | if (err.code === 11000) { 29 | const message = 'Duplicate field value entered' 30 | console.log(err) 31 | error = new ErrorResponse(message, 400) 32 | } 33 | 34 | // Mongoose validation error 35 | if (err.name === 'ValidationError') { 36 | const message: object[] = [] 37 | Object.values(err.errors).forEach((errr: any) => { 38 | message.push({ 39 | field: errr.properties.path, 40 | message: errr.message, 41 | }) 42 | }) 43 | error = new ErrorResponse(null, 400, message) 44 | } 45 | 46 | res.status(error.statusCode || 500).json({ 47 | success: false, 48 | error: error.messageWithField || error.message || 'Server Error', 49 | }) 50 | } 51 | 52 | export default errorHandler 53 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import * as dotenv from 'dotenv' 3 | import 'colors' 4 | import morgan from 'morgan' 5 | import cookieParser from 'cookie-parser' 6 | import mongoSanitize from 'express-mongo-sanitize' 7 | import helmet from 'helmet' 8 | import xss from 'xss-clean' 9 | import rateLimit from 'express-rate-limit' 10 | import hpp from 'hpp' 11 | import cors from 'cors' 12 | 13 | import errorHandler from './middleware/error' 14 | import { AppRouter } from './AppRouter' 15 | 16 | dotenv.config() 17 | 18 | import DBConnection from './config/db' 19 | 20 | DBConnection() 21 | 22 | const app = express() 23 | 24 | app.use(express.json()) 25 | 26 | app.use(cookieParser()) 27 | 28 | if (process.env.NODE_ENV === 'development') { 29 | app.use(morgan('dev')) 30 | } 31 | 32 | // Sanitize data 33 | app.use(mongoSanitize()) 34 | 35 | // Set security headers 36 | app.use(helmet()) 37 | 38 | // Prevent XSS attacks 39 | app.use(xss()) 40 | 41 | // Enable CORS 42 | app.use(cors()) 43 | 44 | // Rate limiting 45 | const limiter = rateLimit({ 46 | windowMs: 10 * 60 * 1000, // 10 mins 47 | max: 100, // 100 request per 10 mins 48 | }) 49 | 50 | app.use(limiter) 51 | 52 | // Prevent http param pollution 53 | app.use(hpp()) 54 | 55 | import './components/users/users.controller' 56 | import './components/auth/auth.controller' 57 | 58 | app.use(AppRouter.getInstance()) 59 | 60 | app.use(errorHandler) 61 | 62 | export default app 63 | -------------------------------------------------------------------------------- /vanilla-js/_data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Sidney Wreight", 4 | "email": "swreight0@mozilla.org", 5 | "password": "KcRwv7jrVf", 6 | "role": "user" 7 | }, 8 | { 9 | "name": "Gothart Philippard", 10 | "email": "gphilippard1@theguardian.com", 11 | "password": "u4I218n", 12 | "role": "user" 13 | }, 14 | { 15 | "name": "Amble Law", 16 | "email": "alaw2@time.com", 17 | "password": "K55jrHdFWEt3", 18 | "role": "user" 19 | }, 20 | { 21 | "name": "Ali Kiddye", 22 | "email": "akiddye3@opensource.org", 23 | "password": "kBrG5cGN1JP", 24 | "role": "user" 25 | }, 26 | { 27 | "name": "Roxie Waldren", 28 | "email": "rwaldren4@theglobeandmail.com", 29 | "password": "7C7a1RrfQ", 30 | "role": "user" 31 | }, 32 | { 33 | "name": "Ardith Mendes", 34 | "email": "amendes5@usda.gov", 35 | "password": "X8Zfcu", 36 | "role": "admin" 37 | }, 38 | { 39 | "name": "Krisha Casino", 40 | "email": "kcasino6@google.pl", 41 | "password": "1UMoFi8a", 42 | "role": "admin" 43 | }, 44 | { 45 | "name": "Lorie Ahrend", 46 | "email": "lahrend7@alibaba.com", 47 | "password": "s5Fynloq", 48 | "role": "admin" 49 | }, 50 | { 51 | "name": "Edin Bernli", 52 | "email": "ebernli8@earthlink.net", 53 | "password": "emQ08I", 54 | "role": "admin" 55 | }, 56 | { 57 | "name": "Pearl Reiglar", 58 | "email": "preiglar9@wunderground.com", 59 | "password": "qu7yPqZ", 60 | "role": "admin" 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /basic-typescript/src/_data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Sidney Wreight", 4 | "email": "swreight0@mozilla.org", 5 | "password": "KcRwv7jrVf", 6 | "role": "user" 7 | }, 8 | { 9 | "name": "Gothart Philippard", 10 | "email": "gphilippard1@theguardian.com", 11 | "password": "u4I218n", 12 | "role": "user" 13 | }, 14 | { 15 | "name": "Amble Law", 16 | "email": "alaw2@time.com", 17 | "password": "K55jrHdFWEt3", 18 | "role": "user" 19 | }, 20 | { 21 | "name": "Ali Kiddye", 22 | "email": "akiddye3@opensource.org", 23 | "password": "kBrG5cGN1JP", 24 | "role": "user" 25 | }, 26 | { 27 | "name": "Roxie Waldren", 28 | "email": "rwaldren4@theglobeandmail.com", 29 | "password": "7C7a1RrfQ", 30 | "role": "user" 31 | }, 32 | { 33 | "name": "Ardith Mendes", 34 | "email": "amendes5@usda.gov", 35 | "password": "X8Zfcu", 36 | "role": "admin" 37 | }, 38 | { 39 | "name": "Krisha Casino", 40 | "email": "kcasino6@google.pl", 41 | "password": "1UMoFi8a", 42 | "role": "admin" 43 | }, 44 | { 45 | "name": "Lorie Ahrend", 46 | "email": "lahrend7@alibaba.com", 47 | "password": "s5Fynloq", 48 | "role": "admin" 49 | }, 50 | { 51 | "name": "Edin Bernli", 52 | "email": "ebernli8@earthlink.net", 53 | "password": "emQ08I", 54 | "role": "admin" 55 | }, 56 | { 57 | "name": "Pearl Reiglar", 58 | "email": "preiglar9@wunderground.com", 59 | "password": "qu7yPqZ", 60 | "role": "admin" 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/_data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Sidney Wreight", 4 | "email": "swreight0@mozilla.org", 5 | "password": "KcRwv7jrVf", 6 | "role": "user" 7 | }, 8 | { 9 | "name": "Gothart Philippard", 10 | "email": "gphilippard1@theguardian.com", 11 | "password": "u4I218n", 12 | "role": "user" 13 | }, 14 | { 15 | "name": "Amble Law", 16 | "email": "alaw2@time.com", 17 | "password": "K55jrHdFWEt3", 18 | "role": "user" 19 | }, 20 | { 21 | "name": "Ali Kiddye", 22 | "email": "akiddye3@opensource.org", 23 | "password": "kBrG5cGN1JP", 24 | "role": "user" 25 | }, 26 | { 27 | "name": "Roxie Waldren", 28 | "email": "rwaldren4@theglobeandmail.com", 29 | "password": "7C7a1RrfQ", 30 | "role": "user" 31 | }, 32 | { 33 | "name": "Ardith Mendes", 34 | "email": "amendes5@usda.gov", 35 | "password": "X8Zfcu", 36 | "role": "admin" 37 | }, 38 | { 39 | "name": "Krisha Casino", 40 | "email": "kcasino6@google.pl", 41 | "password": "1UMoFi8a", 42 | "role": "admin" 43 | }, 44 | { 45 | "name": "Lorie Ahrend", 46 | "email": "lahrend7@alibaba.com", 47 | "password": "s5Fynloq", 48 | "role": "admin" 49 | }, 50 | { 51 | "name": "Edin Bernli", 52 | "email": "ebernli8@earthlink.net", 53 | "password": "emQ08I", 54 | "role": "admin" 55 | }, 56 | { 57 | "name": "Pearl Reiglar", 58 | "email": "preiglar9@wunderground.com", 59 | "password": "qu7yPqZ", 60 | "role": "admin" 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /basic-typescript/src/components/auth/auth.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | import jwt from 'jsonwebtoken' 3 | import asyncHandler from '../../middleware/async' 4 | import ErrorResponse from '../../utils/errorResponse' 5 | import User from '../users/user.model' 6 | 7 | export const protect = asyncHandler( 8 | async (req: Request, _: Response, next: NextFunction) => { 9 | let token 10 | 11 | if ( 12 | req.headers.authorization && 13 | req.headers.authorization.startsWith('Bearer') 14 | ) { 15 | token = req.headers.authorization.split(' ')[1] 16 | } 17 | // Set token from cookie 18 | // else if (req.cookies.token) { 19 | // token = req.cookies.token 20 | // } 21 | 22 | if (!token) { 23 | return next(new ErrorResponse('Not authorized to access this route', 401)) 24 | } 25 | 26 | try { 27 | // Verify token 28 | const decoded = jwt.verify(token, process.env.JWT_SECRET!) 29 | 30 | req.user = (await User.findById((decoded as { id: string }).id)) as any 31 | next() 32 | } catch (err) { 33 | return next(new ErrorResponse('Not authorized to access this route', 401)) 34 | } 35 | } 36 | ) 37 | 38 | // Grant access to specific roles 39 | export const authorize = (...roles: string[]) => { 40 | return (req: Request, _: Response, next: NextFunction) => { 41 | if (!roles.includes(req.user.role)) { 42 | return next( 43 | new ErrorResponse( 44 | `User role ${req.user.role} is not authorized to access this route`, 45 | 403 46 | ) 47 | ) 48 | } 49 | next() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/components/auth/auth.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | import jwt from 'jsonwebtoken' 3 | import asyncHandler from '../../middleware/async' 4 | import ErrorResponse from '../../utils/errorResponse' 5 | import User from '../users/user.model' 6 | 7 | export const protect = asyncHandler( 8 | async (req: Request, _: Response, next: NextFunction) => { 9 | let token 10 | 11 | if ( 12 | req.headers.authorization && 13 | req.headers.authorization.startsWith('Bearer') 14 | ) { 15 | token = req.headers.authorization.split(' ')[1] 16 | } 17 | // Set token from cookie 18 | // else if (req.cookies.token) { 19 | // token = req.cookies.token 20 | // } 21 | 22 | if (!token) { 23 | return next(new ErrorResponse('Not authorized to access this route', 401)) 24 | } 25 | 26 | try { 27 | // Verify token 28 | const decoded = jwt.verify(token, process.env.JWT_SECRET!) 29 | 30 | req.user = (await User.findById((decoded as { id: string }).id)) as any 31 | next() 32 | } catch (err) { 33 | return next(new ErrorResponse('Not authorized to access this route', 401)) 34 | } 35 | } 36 | ) 37 | 38 | // Grant access to specific roles 39 | export const authorize = (...roles: string[]) => { 40 | return (req: Request, _: Response, next: NextFunction) => { 41 | if (!roles.includes(req.user.role)) { 42 | return next( 43 | new ErrorResponse( 44 | `User role ${req.user.role} is not authorized to access this route`, 45 | 403 46 | ) 47 | ) 48 | } 49 | next() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /basic-typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mongoose-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/bin/www", 6 | "scripts": { 7 | "build": "tsc", 8 | "copy-files": "cp -r ./src/emails/ ./dist/", 9 | "start": "node dist/bin/www", 10 | "clean": "rm -rf ./dist", 11 | "dev:copy-files": "cp -r ./src/emails/ ./dist/", 12 | "dev:build": "tsc -w", 13 | "dev:run": "nodemon dist/bin/www", 14 | "dev": "concurrently npm:dev:*" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "MIT", 19 | "dependencies": { 20 | "bcryptjs": "^2.4.3", 21 | "colors": "^1.4.0", 22 | "cookie-parser": "^1.4.6", 23 | "cors": "^2.8.5", 24 | "debug": "^4.3.4", 25 | "dotenv": "^16.0.2", 26 | "email-templates": "^10.0.1", 27 | "express": "^4.18.1", 28 | "express-mongo-sanitize": "^2.2.0", 29 | "express-rate-limit": "^6.6.0", 30 | "helmet": "^6.0.0", 31 | "hpp": "^0.2.3", 32 | "jsonwebtoken": "^8.5.1", 33 | "mongoose": "^6.6.1", 34 | "mongoose-unique-validator": "^3.1.0", 35 | "morgan": "^1.10.0", 36 | "nodemailer": "^6.7.8", 37 | "nunjucks": "^3.2.3", 38 | "typescript": "^4.8.3", 39 | "xss-clean": "^0.1.1" 40 | }, 41 | "devDependencies": { 42 | "@types/bcryptjs": "^2.4.2", 43 | "@types/cookie-parser": "^1.4.3", 44 | "@types/cors": "^2.8.12", 45 | "@types/debug": "^4.1.7", 46 | "@types/dotenv": "^8.2.0", 47 | "@types/email-templates": "^10.0.0", 48 | "@types/express": "^4.17.14", 49 | "@types/hpp": "^0.2.2", 50 | "@types/jsonwebtoken": "^8.5.9", 51 | "@types/mongoose-unique-validator": "^1.0.6", 52 | "@types/morgan": "^1.9.3", 53 | "@types/node": "^18.7.18", 54 | "@types/nodemailer": "^6.4.6", 55 | "concurrently": "^7.4.0", 56 | "nodemon": "^2.0.19" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeJS / ExpressJS MongoDB(Mongoose) Boilerplate 2 | 3 | This is my boilerplate for RESTful API with NodeJS and MongoDB. 4 | 5 | ## Boilerplate Folders 6 | 7 | - advance typescript - typescript version that uses decorators for the boilerplate 8 | - basic typescript - typescript version of the boilerplate 9 | - vanilla js - vanilla javascript boilerplate 10 | 11 | ## Boilerplate Features 12 | 13 | - Authentication with JWT 14 | - Reset Password with email (Using mailtrap for email testing) 15 | - Email verification (Using mailtrap for email testing) 16 | - User Create, Read, Update and Delete (CRUD) operations 17 | - API Security (NoSQL Injections, XSS Attacks, http param pollution etc) 18 | 19 | ## Configuration File 20 | 21 | Modify the config/.env file to your environment variables, set your JWT_SECRET and SMTP variables 22 | 23 | ```ENV 24 | NODE_ENV=development 25 | PORT=3001 26 | 27 | MONGO_URI=YOUR_URL 28 | 29 | JWT_SECRET=YOUR_SECRET 30 | JWT_EXPIRE=30d 31 | JWT_COOKIE_EXPIRE=30 32 | 33 | #In Minutes 34 | RESET_PASSWORD_EXPIRATION_TIME=10 35 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 36 | 37 | SMTP_HOST=smtp.mailtrap.io 38 | SMTP_PORT=2525 39 | SMTP_EMAIL= 40 | SMTP_PASSWORD= 41 | FROM_EMAIL=noreply@boilerplate.com 42 | FROM_NAME=Boilerplate 43 | ``` 44 | 45 | ## Installation 46 | 47 | Install all npm dependecies 48 | 49 | ```console 50 | npm install 51 | ``` 52 | 53 | Install nodemon globally 54 | 55 | ```console 56 | npm install -g nodemon 57 | ``` 58 | 59 | Run database seeder 60 | 61 | ```console 62 | node seeder -i 63 | ``` 64 | 65 | Delete all data 66 | 67 | ```console 68 | node seeder -d 69 | ``` 70 | 71 | ## Run Boilerplate 72 | 73 | ```console 74 | node run dev 75 | ``` 76 | 77 | ## License 78 | 79 | This project is licensed under the MIT License 80 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-mongoose-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/bin/www", 6 | "scripts": { 7 | "build": "tsc", 8 | "copy-files": "cp -r ./src/emails/ ./dist/", 9 | "start": "node dist/bin/www", 10 | "clean": "rm -rf ./dist", 11 | "dev:copy-files": "cp -r ./src/emails/ ./dist/", 12 | "dev:build": "tsc -w", 13 | "dev:run": "nodemon dist/bin/www", 14 | "dev": "concurrently npm:dev:*" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "MIT", 19 | "dependencies": { 20 | "bcryptjs": "^2.4.3", 21 | "colors": "^1.4.0", 22 | "cookie-parser": "^1.4.6", 23 | "cors": "^2.8.5", 24 | "debug": "^4.3.4", 25 | "dotenv": "^16.0.2", 26 | "email-templates": "^10.0.1", 27 | "express": "^4.18.1", 28 | "express-mongo-sanitize": "^2.2.0", 29 | "express-rate-limit": "^6.6.0", 30 | "helmet": "^6.0.0", 31 | "hpp": "^0.2.3", 32 | "jsonwebtoken": "^8.5.1", 33 | "mongoose": "^6.6.1", 34 | "mongoose-unique-validator": "^3.1.0", 35 | "morgan": "^1.10.0", 36 | "nodemailer": "^6.7.8", 37 | "nunjucks": "^3.2.3", 38 | "reflect-metadata": "^0.1.13", 39 | "typescript": "^4.8.3", 40 | "xss-clean": "^0.1.1" 41 | }, 42 | "devDependencies": { 43 | "@types/bcryptjs": "^2.4.2", 44 | "@types/cookie-parser": "^1.4.3", 45 | "@types/cors": "^2.8.12", 46 | "@types/debug": "^4.1.7", 47 | "@types/dotenv": "^8.2.0", 48 | "@types/email-templates": "^10.0.0", 49 | "@types/express": "^4.17.14", 50 | "@types/hpp": "^0.2.2", 51 | "@types/jsonwebtoken": "^8.5.9", 52 | "@types/mongoose-unique-validator": "^1.0.6", 53 | "@types/morgan": "^1.9.3", 54 | "@types/node": "^18.7.18", 55 | "@types/nodemailer": "^6.4.6", 56 | "concurrently": "^7.4.0", 57 | "nodemon": "^2.0.19" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/decorators/controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, RequestHandler, NextFunction } from 'express' 2 | import 'reflect-metadata' 3 | import { AppRouter } from '../AppRouter' 4 | import { MetadataKeys } from './MetadataKeys' 5 | import { Methods } from './Methods' 6 | 7 | import asyncHandler from '../middleware/async' 8 | 9 | function bodyValidators(keys: string): RequestHandler { 10 | return function (req: Request, res: Response, next: NextFunction) { 11 | if (!req.body) { 12 | res.status(422).send('Invalid request') 13 | return 14 | } 15 | 16 | for (let key of keys) { 17 | if (!req.body[key]) { 18 | res.status(422).send(`Missing property ${key}`) 19 | return 20 | } 21 | } 22 | 23 | next() 24 | } 25 | } 26 | 27 | export function controller(routePrefix: string): Function { 28 | return function (target: Function, _: string) { 29 | const router = AppRouter.getInstance() 30 | 31 | for (let key of Object.getOwnPropertyNames(target.prototype)) { 32 | const routeHandler = Object.getOwnPropertyDescriptor( 33 | target.prototype, 34 | key 35 | )?.value 36 | 37 | const path = Reflect.getMetadata(MetadataKeys.path, target.prototype, key) 38 | 39 | const method: Methods = Reflect.getMetadata( 40 | MetadataKeys.method, 41 | target.prototype, 42 | key 43 | ) 44 | 45 | const middlewares = 46 | Reflect.getMetadata(MetadataKeys.middleware, target.prototype, key) || 47 | [] 48 | 49 | const requiredBodyProps = 50 | Reflect.getMetadata(MetadataKeys.validator, target.prototype, key) || [] 51 | 52 | const validator = bodyValidators(requiredBodyProps) 53 | 54 | if (path) { 55 | router[method]( 56 | `${routePrefix}${path}`, 57 | ...middlewares, 58 | validator, 59 | asyncHandler(routeHandler) 60 | ) 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vanilla-js/README.md: -------------------------------------------------------------------------------- 1 | # NodeJS / ExpressJS MongoDB(Mongoose) Boilerplate 2 | 3 | This is my boilerplate for RESTful API with NodeJS and MongoDB. 4 | 5 | ## Boilerplate Branchers 6 | 7 | Switch between branches to get the version you need 8 | 9 | - master branch - advance typescript version that uses decorators for the boilerplate 10 | - typescript branch - typescript version of the boilerplate 11 | - vanilla_js branch - vanilla javascript boilerplate 12 | 13 | ## Boilerplate Features 14 | 15 | - Authentication with JWT 16 | - Reset Password with email (Using mailtrap for email testing) 17 | - Email verification (Using mailtrap for email testing) 18 | - User Create, Read, Update and Delete (CRUD) operations 19 | - API Security (NoSQL Injections, XSS Attacks, http param pollution etc) 20 | 21 | ## Configuration File 22 | 23 | Modify the config/.env file to your environment variables, set your JWT_SECRET and SMTP variables 24 | 25 | ```ENV 26 | NODE_ENV=development 27 | PORT=3001 28 | 29 | MONGO_URI=YOUR_URL 30 | 31 | JWT_SECRET=YOUR_SECRET 32 | JWT_EXPIRE=30d 33 | JWT_COOKIE_EXPIRE=30 34 | 35 | #In Minutes 36 | RESET_PASSWORD_EXPIRATION_TIME=10 37 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 38 | 39 | SMTP_HOST=smtp.mailtrap.io 40 | SMTP_PORT=2525 41 | SMTP_EMAIL= 42 | SMTP_PASSWORD= 43 | FROM_EMAIL=noreply@boilerplate.com 44 | FROM_NAME=Boilerplate 45 | ``` 46 | 47 | ## Installation 48 | 49 | Install all npm dependecies 50 | 51 | ```console 52 | npm install 53 | ``` 54 | 55 | Install nodemon globally 56 | 57 | ```console 58 | npm install -g nodemon 59 | ``` 60 | 61 | Run database seeder 62 | 63 | ```console 64 | node seeder -i 65 | ``` 66 | 67 | Delete all data 68 | 69 | ```console 70 | node seeder -d 71 | ``` 72 | 73 | ## Run Boilerplate 74 | 75 | ```console 76 | node run dev 77 | ``` 78 | 79 | ## License 80 | 81 | This project is licensed under the MIT License 82 | -------------------------------------------------------------------------------- /basic-typescript/README.md: -------------------------------------------------------------------------------- 1 | # NodeJS / ExpressJS MongoDB(Mongoose) Boilerplate 2 | 3 | This is my boilerplate for RESTful API with NodeJS and MongoDB. 4 | 5 | ## Boilerplate Branchers 6 | 7 | Switch between branches to get the version you need 8 | 9 | - master branch - advance typescript version that uses decorators for the boilerplate 10 | - typescript branch - typescript version of the boilerplate 11 | - vanilla_js branch - vanilla javascript boilerplate 12 | 13 | ## Boilerplate Features 14 | 15 | - Authentication with JWT 16 | - Reset Password with email (Using mailtrap for email testing) 17 | - Email verification (Using mailtrap for email testing) 18 | - User Create, Read, Update and Delete (CRUD) operations 19 | - API Security (NoSQL Injections, XSS Attacks, http param pollution etc) 20 | 21 | ## Configuration File 22 | 23 | Modify the config/.env file to your environment variables, set your JWT_SECRET and SMTP variables 24 | 25 | ```ENV 26 | NODE_ENV=development 27 | PORT=3001 28 | 29 | MONGO_URI=YOUR_URL 30 | 31 | JWT_SECRET=YOUR_SECRET 32 | JWT_EXPIRE=30d 33 | JWT_COOKIE_EXPIRE=30 34 | 35 | #In Minutes 36 | RESET_PASSWORD_EXPIRATION_TIME=10 37 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 38 | 39 | SMTP_HOST=smtp.mailtrap.io 40 | SMTP_PORT=2525 41 | SMTP_EMAIL= 42 | SMTP_PASSWORD= 43 | FROM_EMAIL=noreply@boilerplate.com 44 | FROM_NAME=Boilerplate 45 | ``` 46 | 47 | ## Installation 48 | 49 | Install all npm dependecies 50 | 51 | ```console 52 | npm install 53 | ``` 54 | 55 | Install nodemon globally 56 | 57 | ```console 58 | npm install -g nodemon 59 | ``` 60 | 61 | Run database seeder 62 | 63 | ```console 64 | node seeder -i 65 | ``` 66 | 67 | Delete all data 68 | 69 | ```console 70 | node seeder -d 71 | ``` 72 | 73 | ## Run Boilerplate 74 | 75 | ```console 76 | node run dev 77 | ``` 78 | 79 | ## License 80 | 81 | This project is licensed under the MIT License 82 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/README.md: -------------------------------------------------------------------------------- 1 | # NodeJS / ExpressJS MongoDB(Mongoose) Boilerplate 2 | 3 | This is my boilerplate for RESTful API with NodeJS and MongoDB. 4 | 5 | ## Boilerplate Branchers 6 | 7 | Switch between branches to get the version you need 8 | 9 | - master branch - advance typescript version that uses decorators for the boilerplate 10 | - typescript branch - typescript version of the boilerplate 11 | - vanilla_js branch - vanilla javascript boilerplate 12 | 13 | ## Boilerplate Features 14 | 15 | - Authentication with JWT 16 | - Reset Password with email (Using mailtrap for email testing) 17 | - Email verification (Using mailtrap for email testing) 18 | - User Create, Read, Update and Delete (CRUD) operations 19 | - API Security (NoSQL Injections, XSS Attacks, http param pollution etc) 20 | 21 | ## Configuration File 22 | 23 | Modify the config/.env file to your environment variables, set your JWT_SECRET and SMTP variables 24 | 25 | ```ENV 26 | NODE_ENV=development 27 | PORT=3001 28 | 29 | MONGO_URI=YOUR_URL 30 | 31 | JWT_SECRET=YOUR_SECRET 32 | JWT_EXPIRE=30d 33 | JWT_COOKIE_EXPIRE=30 34 | 35 | #In Minutes 36 | RESET_PASSWORD_EXPIRATION_TIME=10 37 | EMAIL_VERIFICATION_EXPIRATION_TIME=10 38 | 39 | SMTP_HOST=smtp.mailtrap.io 40 | SMTP_PORT=2525 41 | SMTP_EMAIL= 42 | SMTP_PASSWORD= 43 | FROM_EMAIL=noreply@boilerplate.com 44 | FROM_NAME=Boilerplate 45 | ``` 46 | 47 | ## Installation 48 | 49 | Install all npm dependecies 50 | 51 | ```console 52 | npm install 53 | ``` 54 | 55 | Install nodemon globally 56 | 57 | ```console 58 | npm install -g nodemon 59 | ``` 60 | 61 | Run database seeder 62 | 63 | ```console 64 | node seeder -i 65 | ``` 66 | 67 | Delete all data 68 | 69 | ```console 70 | node seeder -d 71 | ``` 72 | 73 | ## Run Boilerplate 74 | 75 | ```console 76 | node run dev 77 | ``` 78 | 79 | ## License 80 | 81 | This project is licensed under the MIT License 82 | -------------------------------------------------------------------------------- /vanilla-js/middleware/advancedResults.js: -------------------------------------------------------------------------------- 1 | // @eg housing=true&select=name,location.city&sort=-name,location.state 2 | // ?averageCost[lte]=10000 3 | const advancedResults = (model, populate) => async (req, res, next) => { 4 | let query 5 | 6 | const reqQuery = { ...req.query } 7 | 8 | const removeFields = ['select', 'sort', 'page', 'limit'] 9 | removeFields.forEach(param => delete reqQuery[param]) 10 | 11 | let queryStr = JSON.stringify(reqQuery) 12 | queryStr = queryStr.replace(/\b(gt|gte|lt|lte|in)\b/g, match => `$${match}`) 13 | 14 | query = model.find(JSON.parse(queryStr)) 15 | 16 | if (req.query.select) { 17 | const fields = req.query.select.split(',').join(' ') 18 | query = query.select(fields) 19 | } 20 | 21 | if (req.query.sort) { 22 | const sortBy = req.query.sort.split(',').join(' ') 23 | query = query.sort(sortBy) 24 | } else { 25 | query = query.sort({ createdAt: -1 }) 26 | // '-createdAt' 27 | } 28 | 29 | // Pagination 30 | const page = parseInt(req.query.page, 10) || 1 31 | const limit = parseInt(req.query.limit, 10) || 20 32 | const startIndex = (page - 1) * limit 33 | const endIndex = page * limit 34 | const total = await model.countDocuments() 35 | const totalPage = Math.ceil(total / limit) 36 | 37 | query = query.skip(startIndex).limit(limit) 38 | 39 | if (populate) { 40 | query = query.populate(populate) 41 | } 42 | 43 | const results = await query 44 | 45 | // Pagination result 46 | const pagination = {} 47 | 48 | if (endIndex < total) { 49 | pagination.next = { 50 | page: page + 1, 51 | limit 52 | } 53 | } 54 | 55 | if (startIndex > 0) { 56 | pagination.prev = { 57 | page: page - 1, 58 | limit 59 | } 60 | } 61 | 62 | res.advancedResults = { 63 | success: true, 64 | count: results.length, 65 | totalPage, 66 | pagination, 67 | data: results 68 | } 69 | next() 70 | } 71 | 72 | module.exports = advancedResults 73 | -------------------------------------------------------------------------------- /vanilla-js/components/users/users.controller.js: -------------------------------------------------------------------------------- 1 | const asyncHandler = require('../../middleware/async') 2 | const ErrorResponse = require('../../utils/errorResponse') 3 | const User = require('./user.model') 4 | 5 | // @desc Get all users 6 | // @route GET /api/v1/auth/users 7 | // @access Private/Admin 8 | exports.getUsers = asyncHandler(async (req, res, next) => { 9 | res.status(200).json(res.advancedResults) 10 | }) 11 | 12 | // @desc Get single user 13 | // @route GET /api/v1/auth/users/:id 14 | // @access Private/Admin 15 | exports.getUser = asyncHandler(async (req, res, next) => { 16 | const user = await User.findById(req.params.id) 17 | 18 | if (!user) 19 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 20 | 21 | res.status(200).json({ success: true, data: user }) 22 | }) 23 | 24 | // @desc Create user 25 | // @route POST /api/v1/auth/users 26 | // @access Private/Admin 27 | exports.createUser = asyncHandler(async (req, res, next) => { 28 | const user = await User.create(req.body) 29 | 30 | res.status(201).json({ success: true, data: user }) 31 | }) 32 | 33 | // @desc Update user 34 | // @route PUT /api/v1/auth/users/:id 35 | // @access Private/Admin 36 | exports.updateUser = asyncHandler(async (req, res, next) => { 37 | req.body.password = '' 38 | delete req.body.password 39 | 40 | const user = await User.findByIdAndUpdate(req.params.id, req.body, { 41 | new: true, 42 | runValidators: true, 43 | }) 44 | 45 | if (!user) 46 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 47 | 48 | res.status(200).json({ success: true, data: user }) 49 | }) 50 | 51 | // @desc Delete user 52 | // @route DELETE /api/v1/auth/users/:id 53 | // @access Private/Admin 54 | exports.deleteUser = asyncHandler(async (req, res, next) => { 55 | const user = await User.findById(req.params.id) 56 | 57 | if (!user) 58 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 59 | 60 | await User.findByIdAndDelete(req.params.id) 61 | 62 | res.status(200).json({ success: true, data: {} }) 63 | }) 64 | -------------------------------------------------------------------------------- /basic-typescript/src/middleware/advancedResults.ts: -------------------------------------------------------------------------------- 1 | // @eg housing=true&select=name,location.city&sort=-name,location.state 2 | 3 | import { NextFunction, Request, Response } from 'express' 4 | 5 | // ?averageCost[lte]=10000 6 | const advancedResults = 7 | (model: any, populate: [] = []) => 8 | async (req: Request, res: Response, next: NextFunction) => { 9 | let query 10 | 11 | const reqQuery = { ...req.query } 12 | 13 | const removeFields = ['select', 'sort', 'page', 'limit'] 14 | removeFields.forEach((param) => delete reqQuery[param]) 15 | 16 | let queryStr = JSON.stringify(reqQuery) 17 | queryStr = queryStr.replace( 18 | /\b(gt|gte|lt|lte|in)\b/g, 19 | (match) => `$${match}` 20 | ) 21 | 22 | query = model.find(JSON.parse(queryStr)) 23 | 24 | if (req.query.select) { 25 | const fields = (req.query.select as string).split(',').join(' ') 26 | query = query.select(fields) 27 | } 28 | 29 | if (req.query.sort) { 30 | const sortBy = (req.query.sort as string).split(',').join(' ') 31 | query = query.sort(sortBy) 32 | } else { 33 | query = query.sort({ createdAt: -1 }) 34 | // '-createdAt' 35 | } 36 | 37 | // Pagination 38 | const page = parseInt(req.query.page as string, 10) || 1 39 | const limit = parseInt(req.query.limit as string, 10) || 20 40 | const startIndex = (page - 1) * limit 41 | const endIndex = page * limit 42 | const total = await model.countDocuments() 43 | const totalPage = Math.ceil(total / limit) 44 | 45 | query = query.skip(startIndex).limit(limit) 46 | 47 | if (populate) { 48 | query = query.populate(populate) 49 | } 50 | 51 | const results = await query 52 | 53 | // Pagination result 54 | const pagination = { next: {}, prev: {} } 55 | 56 | if (endIndex < total) { 57 | pagination.next = { 58 | page: page + 1, 59 | limit, 60 | } 61 | } 62 | 63 | if (startIndex > 0) { 64 | pagination.prev = { 65 | page: page - 1, 66 | limit, 67 | } 68 | } 69 | 70 | res.advancedResults = { 71 | success: true, 72 | count: results.length, 73 | totalPage, 74 | pagination, 75 | data: results, 76 | } 77 | next() 78 | } 79 | 80 | export default advancedResults 81 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/middleware/advancedResults.ts: -------------------------------------------------------------------------------- 1 | // @eg housing=true&select=name,location.city&sort=-name,location.state 2 | 3 | import { NextFunction, Request, Response } from 'express' 4 | 5 | // ?averageCost[lte]=10000 6 | const advancedResults = 7 | (model: any, populate: [] = []) => 8 | async (req: Request, res: Response, next: NextFunction) => { 9 | let query 10 | 11 | const reqQuery = { ...req.query } 12 | 13 | const removeFields = ['select', 'sort', 'page', 'limit'] 14 | removeFields.forEach((param) => delete reqQuery[param]) 15 | 16 | let queryStr = JSON.stringify(reqQuery) 17 | queryStr = queryStr.replace( 18 | /\b(gt|gte|lt|lte|in)\b/g, 19 | (match) => `$${match}` 20 | ) 21 | 22 | query = model.find(JSON.parse(queryStr)) 23 | 24 | if (req.query.select) { 25 | const fields = (req.query.select as string).split(',').join(' ') 26 | query = query.select(fields) 27 | } 28 | 29 | if (req.query.sort) { 30 | const sortBy = (req.query.sort as string).split(',').join(' ') 31 | query = query.sort(sortBy) 32 | } else { 33 | query = query.sort({ createdAt: -1 }) 34 | // '-createdAt' 35 | } 36 | 37 | // Pagination 38 | const page = parseInt(req.query.page as string, 10) || 1 39 | const limit = parseInt(req.query.limit as string, 10) || 20 40 | const startIndex = (page - 1) * limit 41 | const endIndex = page * limit 42 | const total = await model.countDocuments() 43 | const totalPage = Math.ceil(total / limit) 44 | 45 | query = query.skip(startIndex).limit(limit) 46 | 47 | if (populate) { 48 | query = query.populate(populate) 49 | } 50 | 51 | const results = await query 52 | 53 | // Pagination result 54 | const pagination = { next: {}, prev: {} } 55 | 56 | if (endIndex < total) { 57 | pagination.next = { 58 | page: page + 1, 59 | limit, 60 | } 61 | } 62 | 63 | if (startIndex > 0) { 64 | pagination.prev = { 65 | page: page - 1, 66 | limit, 67 | } 68 | } 69 | 70 | res.advancedResults = { 71 | success: true, 72 | count: results.length, 73 | totalPage, 74 | pagination, 75 | data: results, 76 | } 77 | next() 78 | } 79 | 80 | export default advancedResults 81 | -------------------------------------------------------------------------------- /basic-typescript/src/components/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | 3 | import asyncHandler from '../../middleware/async' 4 | import ErrorResponse from '../../utils/errorResponse' 5 | import User from './user.model' 6 | 7 | // @desc Get all users 8 | // @route GET /api/v1/auth/users 9 | // @access Private/Admin 10 | export const getUsers = asyncHandler(async (_: Request, res: Response) => { 11 | res.status(200).json(res.advancedResults) 12 | }) 13 | 14 | // @desc Get single user 15 | // @route GET /api/v1/auth/users/:id 16 | // @access Private/Admin 17 | export const getUser = asyncHandler( 18 | async (req: Request, res: Response, next: NextFunction) => { 19 | const user = await User.findById(req.params.id) 20 | 21 | if (!user) 22 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 23 | 24 | res.status(200).json({ success: true, data: user }) 25 | } 26 | ) 27 | 28 | // @desc Create user 29 | // @route POST /api/v1/auth/users 30 | // @access Private/Admin 31 | export const createUser = asyncHandler(async (req: Request, res: Response) => { 32 | const user = await User.create(req.body) 33 | 34 | res.status(201).json({ success: true, data: user }) 35 | }) 36 | 37 | // @desc Update user 38 | // @route PUT /api/v1/auth/users/:id 39 | // @access Private/Admin 40 | export const updateUser = asyncHandler( 41 | async (req: Request, res: Response, next: NextFunction) => { 42 | req.body.password = '' 43 | delete req.body.password 44 | 45 | const user = await User.findByIdAndUpdate(req.params.id, req.body, { 46 | new: true, 47 | runValidators: true, 48 | }) 49 | 50 | if (!user) 51 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 52 | 53 | res.status(200).json({ success: true, data: user }) 54 | } 55 | ) 56 | 57 | // @desc Delete user 58 | // @route DELETE /api/v1/auth/users/:id 59 | // @access Private/Admin 60 | export const deleteUser = asyncHandler( 61 | async (req: Request, res: Response, next: NextFunction) => { 62 | const user = await User.findById(req.params.id) 63 | 64 | if (!user) 65 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 66 | 67 | await User.findByIdAndDelete(req.params.id) 68 | 69 | res.status(200).json({ success: true, data: {} }) 70 | } 71 | ) 72 | -------------------------------------------------------------------------------- /vanilla-js/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const app = require('../app') 8 | const debug = require('debug')( 9 | 'nodejs-and-expressjs-with-mongodb-boilerplate:server' 10 | ) 11 | const http = require('http') 12 | 13 | /** 14 | * Get port from environment and store in Express. 15 | */ 16 | 17 | const port = normalizePort(process.env.PORT || '3000') 18 | app.set('port', port) 19 | 20 | /** 21 | * Create HTTP server. 22 | */ 23 | 24 | const server = http.createServer(app) 25 | 26 | /** 27 | * Listen on provided port, on all network interfaces. 28 | */ 29 | 30 | app.listen(port, () => { 31 | console.log( 32 | `We are live on ${process.env.NODE_ENV} mode on port ${port}`.yellow.bold 33 | ) 34 | }) 35 | 36 | server.on('error', onError) 37 | server.on('listening', onListening) 38 | 39 | // Handle unhandled promise rejections 40 | process.on('unhandledRejection', (err, promise) => { 41 | console.log(`Error: ${err.message}`.red) 42 | // Close server & exit process 43 | server.close(() => process.exit(1)) 44 | }) 45 | 46 | /** 47 | * Normalize a port into a number, string, or false. 48 | */ 49 | 50 | function normalizePort(val) { 51 | const port = parseInt(val, 10) 52 | 53 | if (isNaN(port)) { 54 | // named pipe 55 | return val 56 | } 57 | 58 | if (port >= 0) { 59 | // port number 60 | return port 61 | } 62 | 63 | return false 64 | } 65 | 66 | /** 67 | * Event listener for HTTP server "error" event. 68 | */ 69 | 70 | function onError(error) { 71 | if (error.syscall !== 'listen') { 72 | throw error 73 | } 74 | 75 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port 76 | 77 | // handle specific listen errors with friendly messages 78 | switch (error.code) { 79 | case 'EACCES': 80 | console.error(bind + ' requires elevated privileges') 81 | process.exit(1) 82 | break 83 | case 'EADDRINUSE': 84 | console.error(bind + ' is already in use') 85 | process.exit(1) 86 | break 87 | default: 88 | throw error 89 | } 90 | } 91 | 92 | /** 93 | * Event listener for HTTP server "listening" event. 94 | */ 95 | 96 | function onListening() { 97 | const addr = server.address() 98 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port 99 | debug('Listening on ' + bind) 100 | } 101 | -------------------------------------------------------------------------------- /basic-typescript/src/bin/www.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | import app from '../app' 8 | import debug from 'debug' 9 | debug('nodejs-and-expressjs-with-mongodb-boilerplate:server') 10 | import http from 'http' 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | 16 | const port = normalizePort(process.env.PORT || '3000') 17 | app.set('port', port) 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | const server = http.createServer(app) 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | 29 | app.listen(port, () => { 30 | console.log( 31 | `We are live on ${process.env.NODE_ENV} mode on port ${port}`.yellow.bold 32 | ) 33 | }) 34 | 35 | server.on('error', onError) 36 | server.on('listening', onListening) 37 | 38 | // Handle unhandled promise rejections 39 | process.on('unhandledRejection', (err: { message: string }, _) => { 40 | console.log(`Error: ${err.message}`.red) 41 | // Close server & exit process 42 | server.close(() => process.exit(1)) 43 | }) 44 | 45 | /** 46 | * Normalize a port into a number, string, or false. 47 | */ 48 | 49 | function normalizePort(val: string) { 50 | const port = parseInt(val, 10) 51 | 52 | if (isNaN(port)) { 53 | // named pipe 54 | return val 55 | } 56 | 57 | if (port >= 0) { 58 | // port number 59 | return port 60 | } 61 | 62 | return false 63 | } 64 | 65 | /** 66 | * Event listener for HTTP server "error" event. 67 | */ 68 | 69 | function onError(error: any) { 70 | if (error.syscall !== 'listen') { 71 | throw error 72 | } 73 | 74 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port 75 | 76 | // handle specific listen errors with friendly messages 77 | switch (error.code) { 78 | case 'EACCES': 79 | console.error(bind + ' requires elevated privileges') 80 | process.exit(1) 81 | break 82 | case 'EADDRINUSE': 83 | console.error(bind + ' is already in use') 84 | process.exit(1) 85 | break 86 | default: 87 | throw error 88 | } 89 | } 90 | 91 | /** 92 | * Event listener for HTTP server "listening" event. 93 | */ 94 | 95 | function onListening() { 96 | const addr = server.address() 97 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr?.port 98 | debug('Listening on ' + bind) 99 | } 100 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/bin/www.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | import app from '../app' 8 | import debug from 'debug' 9 | debug('nodejs-and-expressjs-with-mongodb-boilerplate:server') 10 | import http from 'http' 11 | 12 | /** 13 | * Get port from environment and store in Express. 14 | */ 15 | 16 | const port = normalizePort(process.env.PORT || '3000') 17 | app.set('port', port) 18 | 19 | /** 20 | * Create HTTP server. 21 | */ 22 | 23 | const server = http.createServer(app) 24 | 25 | /** 26 | * Listen on provided port, on all network interfaces. 27 | */ 28 | 29 | app.listen(port, () => { 30 | console.log( 31 | `We are live on ${process.env.NODE_ENV} mode on port ${port}`.yellow.bold 32 | ) 33 | }) 34 | 35 | server.on('error', onError) 36 | server.on('listening', onListening) 37 | 38 | // Handle unhandled promise rejections 39 | process.on('unhandledRejection', (err: { message: string }, _) => { 40 | console.log(`Error: ${err.message}`.red) 41 | // Close server & exit process 42 | server.close(() => process.exit(1)) 43 | }) 44 | 45 | /** 46 | * Normalize a port into a number, string, or false. 47 | */ 48 | 49 | function normalizePort(val: string) { 50 | const port = parseInt(val, 10) 51 | 52 | if (isNaN(port)) { 53 | // named pipe 54 | return val 55 | } 56 | 57 | if (port >= 0) { 58 | // port number 59 | return port 60 | } 61 | 62 | return false 63 | } 64 | 65 | /** 66 | * Event listener for HTTP server "error" event. 67 | */ 68 | 69 | function onError(error: any) { 70 | if (error.syscall !== 'listen') { 71 | throw error 72 | } 73 | 74 | const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port 75 | 76 | // handle specific listen errors with friendly messages 77 | switch (error.code) { 78 | case 'EACCES': 79 | console.error(bind + ' requires elevated privileges') 80 | process.exit(1) 81 | break 82 | case 'EADDRINUSE': 83 | console.error(bind + ' is already in use') 84 | process.exit(1) 85 | break 86 | default: 87 | throw error 88 | } 89 | } 90 | 91 | /** 92 | * Event listener for HTTP server "listening" event. 93 | */ 94 | 95 | function onListening() { 96 | const addr = server.address() 97 | const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr?.port 98 | debug('Listening on ' + bind) 99 | } 100 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/components/users/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express' 2 | 3 | import User from './user.model' 4 | 5 | import { get, controller, post, use, put, del } from '../../decorators' 6 | import ErrorResponse from '../../utils/errorResponse' 7 | import advancedResults from '../../middleware/advancedResults' 8 | import { protect, authorize } from '../auth/auth.middleware' 9 | 10 | @controller('/api/v1/users') 11 | // @ts-ignore 12 | class UserController { 13 | // @desc Get all users 14 | // @route GET /api/v1/users 15 | // @access Private/Admin 16 | @get('/') 17 | @use(advancedResults(User)) 18 | @use(authorize('admin')) 19 | @use(protect) 20 | async getUsers(_: Request, res: Response) { 21 | res.status(200).json(res.advancedResults) 22 | } 23 | 24 | // @desc Get single user 25 | // @route GET /api/v1/users/:id 26 | // @access Private/Admin 27 | @get('/:id') 28 | @use(authorize('admin')) 29 | @use(protect) 30 | async getUser(req: Request, res: Response, next: NextFunction) { 31 | const user = await User.findById(req.params.id) 32 | 33 | if (!user) 34 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 35 | 36 | res.status(200).json({ success: true, data: user }) 37 | } 38 | 39 | // @desc Create user 40 | // @route POST /api/v1/users 41 | // @access Private/Admin 42 | @post('/') 43 | @use(authorize('admin')) 44 | @use(protect) 45 | async createUser(req: Request, res: Response) { 46 | const user = await User.create(req.body) 47 | 48 | res.status(201).json({ success: true, data: user }) 49 | } 50 | 51 | // @desc Update user 52 | // @route PUT /api/v1/users/:id 53 | // @access Private/Admin 54 | @put('/:id') 55 | @use(authorize('admin')) 56 | @use(protect) 57 | async updateUser(req: Request, res: Response, next: NextFunction) { 58 | req.body.password = '' 59 | delete req.body.password 60 | 61 | const user = await User.findByIdAndUpdate(req.params.id, req.body, { 62 | new: true, 63 | runValidators: true, 64 | }) 65 | 66 | if (!user) 67 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 68 | 69 | res.status(200).json({ success: true, data: user }) 70 | } 71 | 72 | // @desc Delete user 73 | // @route DELETE /api/v1/users/:id 74 | // @access Private/Admin 75 | @del('/:id') 76 | @use(authorize('admin')) 77 | @use(protect) 78 | async deleteUser(req: Request, res: Response, next: NextFunction) { 79 | const user = await User.findById(req.params.id) 80 | 81 | if (!user) 82 | return next(new ErrorResponse(`No user with that id of ${req.params.id}`)) 83 | 84 | await User.findByIdAndDelete(req.params.id) 85 | 86 | res.status(200).json({ success: true, data: {} }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /vanilla-js/components/users/user.model.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const mongoose = require('mongoose') 3 | const bcrypt = require('bcryptjs') 4 | const jwt = require('jsonwebtoken') 5 | 6 | const uniqueValidator = require('mongoose-unique-validator') 7 | 8 | const Schema = mongoose.Schema 9 | 10 | const UserSchema = new Schema( 11 | { 12 | name: { 13 | type: String, 14 | required: [true, 'Please add a name'], 15 | }, 16 | email: { 17 | type: String, 18 | required: [true, 'Please add an email'], 19 | unique: true, 20 | uniqueCaseInsensitive: true, 21 | match: [ 22 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 23 | 'Please add a valid email', 24 | ], 25 | }, 26 | role: { 27 | type: String, 28 | enum: ['user', 'admin'], 29 | default: 'user', 30 | }, 31 | password: { 32 | type: String, 33 | required: [true, 'Please add a password'], 34 | minlength: [6, 'Must be six characters long'], 35 | select: false, 36 | }, 37 | isEmailVerified: { 38 | type: Boolean, 39 | default: false, 40 | }, 41 | emailVerificationToken: String, 42 | emailVerificationExpire: Date, 43 | resetPasswordToken: String, 44 | resetPasswordExpire: Date, 45 | }, 46 | { 47 | timestamps: true, 48 | } 49 | ) 50 | 51 | UserSchema.plugin(uniqueValidator, { message: '{PATH} already exists.' }) 52 | 53 | // Ecrypt Password 54 | UserSchema.pre('save', async function (next) { 55 | if (!this.isModified('password')) { 56 | next() 57 | } 58 | 59 | const salt = await bcrypt.genSalt(10) 60 | this.password = await bcrypt.hash(this.password, salt) 61 | }) 62 | 63 | UserSchema.methods.matchPassword = async function (enteredPassword) { 64 | return await bcrypt.compare(enteredPassword, this.password) 65 | } 66 | 67 | UserSchema.methods.getSignedJwtToken = function () { 68 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET, { 69 | expiresIn: process.env.JWT_EXPIRE, 70 | }) 71 | } 72 | 73 | UserSchema.methods.getEmailVerificationToken = function () { 74 | const resetToken = crypto.randomBytes(20).toString('hex') 75 | 76 | this.emailVerificationToken = crypto 77 | .createHash('sha256') 78 | .update(resetToken) 79 | .digest('hex') 80 | 81 | this.emailVerificationExpire = 82 | Date.now() + 60 * 1000 * process.env.EMAIL_VERIFICATION_EXPIRATION_TIME 83 | 84 | return resetToken 85 | } 86 | 87 | UserSchema.methods.getResetPasswordToken = function () { 88 | // Generate token 89 | const resetToken = crypto.randomBytes(20).toString('hex') 90 | 91 | // Hash token and set to resetPasswordToken field 92 | this.resetPasswordToken = crypto 93 | .createHash('sha256') 94 | .update(resetToken) 95 | .digest('hex') 96 | 97 | // Set expire 98 | this.resetPasswordExpire = 99 | Date.now() + 60 * 1000 * process.env.RESET_PASSWORD_EXPIRATION_TIME 100 | 101 | return resetToken 102 | } 103 | 104 | module.exports = mongoose.model('User', UserSchema) 105 | -------------------------------------------------------------------------------- /basic-typescript/src/components/users/user.model.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import { Schema, Model, model } from 'mongoose' 3 | import bcrypt from 'bcryptjs' 4 | import jwt from 'jsonwebtoken' 5 | 6 | import uniqueValidator from 'mongoose-unique-validator' 7 | 8 | export interface IUserMethods { 9 | matchPassword(password: string): Promise