├── 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 | 8 | 9 | 10 | 11 |

Email Verification

12 | 13 |

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 | 8 | 9 | 10 | 11 |

Email Verification

12 | 13 |

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 | 8 | 9 | 10 | 11 |

Email Verification

12 | 13 |

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 | 8 | 9 | 10 | 11 |

Forgot Password

12 | 13 |

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 | 8 | 9 | 10 | 11 |

Forgot Password

12 | 13 |

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 | 8 | 9 | 10 | 11 |

Forgot Password

12 | 13 |

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 10 | getSignedJwtToken(): string 11 | getEmailVerificationToken(): string 12 | getResetPasswordToken(): string 13 | } 14 | 15 | export interface IUser extends IUserMethods { 16 | name: string 17 | email: string 18 | role?: string 19 | password: string 20 | isEmailVerified: boolean 21 | emailVerificationToken: string | undefined 22 | emailVerificationExpire: Date | undefined 23 | resetPasswordToken: string | undefined 24 | resetPasswordExpire: Date | undefined 25 | } 26 | 27 | type UserModel = Model 28 | 29 | const UserSchema = new Schema( 30 | { 31 | name: { 32 | type: String, 33 | required: [true, 'Please add a name'], 34 | }, 35 | email: { 36 | type: String, 37 | required: [true, 'Please add an email'], 38 | unique: true, 39 | uniqueCaseInsensitive: true, 40 | match: [ 41 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 42 | 'Please add a valid email', 43 | ], 44 | }, 45 | role: { 46 | type: String, 47 | enum: ['user', 'admin'], 48 | default: 'user', 49 | }, 50 | password: { 51 | type: String, 52 | required: [true, 'Please add a password'], 53 | minlength: [6, 'Must be six characters long'], 54 | select: false, 55 | }, 56 | isEmailVerified: { 57 | type: Boolean, 58 | default: false, 59 | }, 60 | emailVerificationToken: String, 61 | emailVerificationExpire: Date, 62 | resetPasswordToken: String, 63 | resetPasswordExpire: Date, 64 | }, 65 | { 66 | timestamps: true, 67 | } 68 | ) 69 | 70 | UserSchema.plugin(uniqueValidator, { message: '{PATH} already exists.' }) 71 | 72 | // Ecrypt Password 73 | UserSchema.pre('save', async function (next) { 74 | if (!this.isModified('password')) { 75 | next() 76 | } 77 | 78 | const salt = await bcrypt.genSalt(10) 79 | this.password = await bcrypt.hash(this.password, salt) 80 | }) 81 | 82 | UserSchema.methods.matchPassword = async function (enteredPassword: string) { 83 | return await bcrypt.compare(enteredPassword, this.password) 84 | } 85 | 86 | UserSchema.methods.getSignedJwtToken = function () { 87 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET!, { 88 | expiresIn: process.env.JWT_EXPIRE, 89 | }) 90 | } 91 | 92 | UserSchema.methods.getEmailVerificationToken = function () { 93 | const resetToken = crypto.randomBytes(20).toString('hex') 94 | 95 | this.emailVerificationToken = crypto 96 | .createHash('sha256') 97 | .update(resetToken) 98 | .digest('hex') 99 | 100 | // const expirationTime: number = 101 | 102 | this.emailVerificationExpire = 103 | Date.now() + 60 * 1000 * +process.env.EMAIL_VERIFICATION_EXPIRATION_TIME! 104 | 105 | return resetToken 106 | } 107 | 108 | UserSchema.methods.getResetPasswordToken = function () { 109 | // Generate token 110 | const resetToken = crypto.randomBytes(20).toString('hex') 111 | 112 | // Hash token and set to resetPasswordToken field 113 | this.resetPasswordToken = crypto 114 | .createHash('sha256') 115 | .update(resetToken) 116 | .digest('hex') 117 | 118 | // Set expire 119 | this.resetPasswordExpire = 120 | Date.now() + 60 * 1000 * +process.env.RESET_PASSWORD_EXPIRATION_TIME! 121 | 122 | return resetToken 123 | } 124 | 125 | export default model('User', UserSchema) 126 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/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 10 | getSignedJwtToken(): string 11 | getEmailVerificationToken(): string 12 | getResetPasswordToken(): string 13 | } 14 | 15 | export interface IUser extends IUserMethods { 16 | name: string 17 | email: string 18 | role?: string 19 | password: string 20 | isEmailVerified: boolean 21 | emailVerificationToken: string | undefined 22 | emailVerificationExpire: Date | undefined 23 | resetPasswordToken: string | undefined 24 | resetPasswordExpire: Date | undefined 25 | } 26 | 27 | type UserModel = Model 28 | 29 | const UserSchema = new Schema( 30 | { 31 | name: { 32 | type: String, 33 | required: [true, 'Please add a name'], 34 | }, 35 | email: { 36 | type: String, 37 | required: [true, 'Please add an email'], 38 | unique: true, 39 | uniqueCaseInsensitive: true, 40 | match: [ 41 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 42 | 'Please add a valid email', 43 | ], 44 | }, 45 | role: { 46 | type: String, 47 | enum: ['user', 'admin'], 48 | default: 'user', 49 | }, 50 | password: { 51 | type: String, 52 | required: [true, 'Please add a password'], 53 | minlength: [6, 'Must be six characters long'], 54 | select: false, 55 | }, 56 | isEmailVerified: { 57 | type: Boolean, 58 | default: false, 59 | }, 60 | emailVerificationToken: String, 61 | emailVerificationExpire: Date, 62 | resetPasswordToken: String, 63 | resetPasswordExpire: Date, 64 | }, 65 | { 66 | timestamps: true, 67 | } 68 | ) 69 | 70 | UserSchema.plugin(uniqueValidator, { message: '{PATH} already exists.' }) 71 | 72 | // Ecrypt Password 73 | UserSchema.pre('save', async function (next) { 74 | if (!this.isModified('password')) { 75 | next() 76 | } 77 | 78 | const salt = await bcrypt.genSalt(10) 79 | this.password = await bcrypt.hash(this.password, salt) 80 | }) 81 | 82 | UserSchema.methods.matchPassword = async function (enteredPassword: string) { 83 | return await bcrypt.compare(enteredPassword, this.password) 84 | } 85 | 86 | UserSchema.methods.getSignedJwtToken = function () { 87 | return jwt.sign({ id: this._id }, process.env.JWT_SECRET!, { 88 | expiresIn: process.env.JWT_EXPIRE, 89 | }) 90 | } 91 | 92 | UserSchema.methods.getEmailVerificationToken = function () { 93 | const resetToken = crypto.randomBytes(20).toString('hex') 94 | 95 | this.emailVerificationToken = crypto 96 | .createHash('sha256') 97 | .update(resetToken) 98 | .digest('hex') 99 | 100 | // const expirationTime: number = 101 | 102 | this.emailVerificationExpire = 103 | Date.now() + 60 * 1000 * +process.env.EMAIL_VERIFICATION_EXPIRATION_TIME! 104 | 105 | return resetToken 106 | } 107 | 108 | UserSchema.methods.getResetPasswordToken = function () { 109 | // Generate token 110 | const resetToken = crypto.randomBytes(20).toString('hex') 111 | 112 | // Hash token and set to resetPasswordToken field 113 | this.resetPasswordToken = crypto 114 | .createHash('sha256') 115 | .update(resetToken) 116 | .digest('hex') 117 | 118 | // Set expire 119 | this.resetPasswordExpire = 120 | Date.now() + 60 * 1000 * +process.env.RESET_PASSWORD_EXPIRATION_TIME! 121 | 122 | return resetToken 123 | } 124 | 125 | export default model('User', UserSchema) 126 | -------------------------------------------------------------------------------- /vanilla-js/components/auth/auth.controller.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const asyncHandler = require('../../middleware/async') 3 | const ErrorResponse = require('../../utils/errorResponse') 4 | const sendEmail = require('../../utils/sendEmail') 5 | const sendEmailVerification = require('../../utils/sendEmailVerification') 6 | 7 | const User = require('../users/user.model') 8 | 9 | // @desc Register user 10 | // @route POST /api/v1/auth/register 11 | // @access Public 12 | exports.register = asyncHandler(async (req, res, next) => { 13 | let { name, email, password, role } = req.body 14 | 15 | email = email.toLowerCase() 16 | 17 | user = await User.create({ 18 | name, 19 | email, 20 | password, 21 | role, 22 | }) 23 | 24 | sendEmailVerification(user, req) 25 | 26 | sendTokenResponse(user, 200, res) 27 | }) 28 | 29 | // @desc Login user 30 | // @route POST /api/v1/auth/login 31 | // @access Public 32 | exports.login = asyncHandler(async (req, res, next) => { 33 | let { email, password } = req.body 34 | 35 | if (!email || !password) { 36 | return next(new ErrorResponse('Please provide an email and password', 400)) 37 | } 38 | 39 | email = email.toLowerCase() 40 | 41 | const user = await User.findOne({ email }).select('+password') 42 | 43 | if (!user) { 44 | return next(new ErrorResponse('Invalid credentials', 400)) 45 | } 46 | 47 | const isMatch = await user.matchPassword(password) 48 | 49 | if (!isMatch) { 50 | return next(new ErrorResponse('Invalid credentials', 400)) 51 | } 52 | 53 | sendTokenResponse(user, 200, res) 54 | }) 55 | 56 | // @desc Log user out / clear cookie 57 | // @route GET /api/v1/auth/logout 58 | // @access Private 59 | exports.logout = asyncHandler(async (req, res, next) => { 60 | res.cookie('token', 'none', { 61 | expires: new Date(Date.now() + 10 * 1000), 62 | httpOnly: true, 63 | }) 64 | 65 | res.status(200).json({ success: true, data: {} }) 66 | }) 67 | 68 | // @desc Get current logged in user 69 | // @route POST /api/v1/auth/me 70 | // @access Private 71 | exports.getMe = asyncHandler(async (req, res, next) => { 72 | const user = req.user 73 | 74 | res.status(200).json({ success: true, data: user }) 75 | }) 76 | 77 | // @desc Update user details 78 | // @route POST /api/v1/auth/updatedetails 79 | // @access Private 80 | exports.updateDetails = asyncHandler(async (req, res, next) => { 81 | const fieldsToUpdate = { 82 | name: req.body.name, 83 | email: req.body.email.toLowerCase(), 84 | } 85 | const user = await User.findByIdAndUpdate(req.user.id, fieldsToUpdate, { 86 | new: true, 87 | runValidators: true, 88 | context: 'query', 89 | }) 90 | 91 | res.status(200).json({ success: true, data: user }) 92 | }) 93 | 94 | // @desc Update password 95 | // @route PUT /api/v1/auth/updatepassword 96 | // @access Private 97 | exports.updatePassword = asyncHandler(async (req, res, next) => { 98 | const user = await User.findById(req.user.id).select('+password') 99 | 100 | if (!(await user.matchPassword(req.body.currentPassword))) { 101 | return next(new ErrorResponse('Password is incorrect', 401)) 102 | } 103 | 104 | user.password = req.body.newPassword 105 | await user.save() 106 | 107 | sendTokenResponse(user, 200, res) 108 | }) 109 | 110 | // @desc Forgot password 111 | // @route POST /api/v1/auth/forgotpassword 112 | // @access Public 113 | exports.forgotPassword = asyncHandler(async (req, res, next) => { 114 | const user = await User.findOne({ email: req.body.email.toLowerCase() }) 115 | 116 | if (!user) { 117 | return next(new ErrorResponse('There is no user with that email', 404)) 118 | } 119 | 120 | const resetToken = user.getResetPasswordToken() 121 | 122 | await user.save({ validateBeforeSave: false }) 123 | 124 | const resetUrl = `${req.protocol}://${req.get( 125 | 'host' 126 | )}/api/v1/auth/resetpassword/${resetToken}` 127 | 128 | // const message = `You are receiving this email because you (or someone else) has requested the reset of a password. Please make a PUT request to: \n\n ${resetUrl}` 129 | 130 | try { 131 | await sendEmail({ 132 | template: 'forgot-password', 133 | email: user.email, 134 | locals: { 135 | link: resetUrl, 136 | }, 137 | }) 138 | res.status(200).json({ success: true, data: 'Email sent' }) 139 | } catch (err) { 140 | console.log(err) 141 | user.resetPasswordToken = undefined 142 | user.resetPasswordExpire = undefined 143 | 144 | await user.save({ validateBeforeSave: false }) 145 | 146 | return next(new ErrorResponse('Email could not be sent', 500)) 147 | } 148 | }) 149 | 150 | // @desc Reset password 151 | // @route PUT /api/v1/auth/resetpassword/:resettoken 152 | // @access Public 153 | exports.resetPassword = asyncHandler(async (req, res, next) => { 154 | // Get hashed token 155 | const resetPasswordToken = crypto 156 | .createHash('sha256') 157 | .update(req.params.resettoken) 158 | .digest('hex') 159 | 160 | console.log(resetPasswordToken) 161 | 162 | const user = await User.findOne({ 163 | resetPasswordToken, 164 | resetPasswordExpire: { $gt: Date.now() }, 165 | }) 166 | 167 | if (!user) { 168 | return next(new ErrorResponse('Invalid token', 400)) 169 | } 170 | 171 | // Set new password 172 | user.password = req.body.password 173 | user.resetPasswordToken = undefined 174 | user.resetPasswordExpire = undefined 175 | await user.save() 176 | 177 | sendTokenResponse(user, 200, res) 178 | }) 179 | 180 | // @desc Email verification 181 | // @route PUT /api/v1/auth/emailverification/:resettoken 182 | // @access Public 183 | exports.emailVerification = asyncHandler(async (req, res, next) => { 184 | // Get hashed token 185 | const emailVerificationToken = crypto 186 | .createHash('sha256') 187 | .update(req.params.resettoken) 188 | .digest('hex') 189 | 190 | console.log(emailVerificationToken) 191 | 192 | const user = await User.findOne({ 193 | emailVerificationToken, 194 | emailVerificationExpire: { $gt: Date.now() }, 195 | }) 196 | 197 | if (!user) { 198 | return next(new ErrorResponse('Email verification link has expired', 400)) 199 | } 200 | 201 | user.isEmailVerified = true 202 | user.emailVerificationToken = undefined 203 | user.emailVerificationExpire = undefined 204 | await user.save() 205 | 206 | res.status(200).json({ success: true, data: user }) 207 | }) 208 | 209 | // @desc Send Email Verification 210 | // @route POST /api/v1/auth/sendemailverification 211 | // @access Public 212 | exports.sendEmailVerification = asyncHandler(async (req, res, next) => { 213 | if (!req.body.email) { 214 | return next(new ErrorResponse('Email is required', 400)) 215 | } 216 | 217 | const user = await User.findOne({ 218 | email: req.body.email, 219 | isEmailVerified: false, 220 | }) 221 | 222 | if (!user) { 223 | return next( 224 | new ErrorResponse( 225 | 'User already verified or No user with that email address', 226 | 400 227 | ) 228 | ) 229 | } 230 | 231 | const isSent = sendEmailVerification(user, req) 232 | isSent.then((data) => { 233 | if (!data) { 234 | return next(new ErrorResponse('Email could not be sent', 500)) 235 | } 236 | return res.status(200).json({ success: true, data: 'Email sent' }) 237 | }) 238 | }) 239 | 240 | // Get token from model, create cookie and send response 241 | const sendTokenResponse = (user, statusCode, res) => { 242 | const token = user.getSignedJwtToken() 243 | 244 | const options = { 245 | expires: new Date( 246 | Date.now() + process.env.JWT_COOKIE_EXPIRE * 24 * 60 * 60 * 1000 247 | ), 248 | httpOnly: true, 249 | } 250 | 251 | if (process.env.NODE_ENV === 'production') { 252 | options.secure = true 253 | } 254 | 255 | res 256 | .status(statusCode) 257 | .cookie('token', token, options) 258 | .json({ success: true, token }) 259 | } 260 | -------------------------------------------------------------------------------- /basic-typescript/src/components/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import { NextFunction, Request, Response } from 'express' 3 | import asyncHandler from '../../middleware/async' 4 | import ErrorResponse from '../../utils/errorResponse' 5 | import sendEmail from '../../utils/sendEmail' 6 | import sendEmailVerification from '../../utils/sendEmailVerification' 7 | 8 | import User, { IUser } from '../users/user.model' 9 | 10 | // @desc Register user 11 | // @route POST /api/v1/auth/register 12 | // @access Public 13 | export const register = asyncHandler(async (req: Request, res: Response) => { 14 | let { name, email, password, role } = req.body 15 | 16 | email = email.toLowerCase() 17 | 18 | const user = await User.create({ 19 | name, 20 | email, 21 | password, 22 | role, 23 | }) 24 | 25 | sendEmailVerification(user, req) 26 | 27 | sendTokenResponse(user, 200, res) 28 | }) 29 | 30 | // @desc Login user 31 | // @route POST /api/v1/auth/login 32 | // @access Public 33 | export const login = asyncHandler( 34 | async (req: Request, res: Response, next: NextFunction) => { 35 | let { email, password } = req.body 36 | 37 | if (!email || !password) { 38 | return next( 39 | new ErrorResponse('Please provide an email and password', 400) 40 | ) 41 | } 42 | 43 | email = email.toLowerCase() 44 | 45 | const user = await User.findOne({ email }).select('+password') 46 | 47 | if (!user) { 48 | return next(new ErrorResponse('Invalid credentials', 400)) 49 | } 50 | 51 | const isMatch = await user.matchPassword(password) 52 | 53 | if (!isMatch) { 54 | return next(new ErrorResponse('Invalid credentials', 400)) 55 | } 56 | 57 | sendTokenResponse(user, 200, res) 58 | } 59 | ) 60 | 61 | // @desc Log user out / clear cookie 62 | // @route GET /api/v1/auth/logout 63 | // @access Private 64 | export const logout = asyncHandler(async (_: Request, res: Response) => { 65 | res.cookie('token', 'none', { 66 | expires: new Date(Date.now() + 10 * 1000), 67 | httpOnly: true, 68 | }) 69 | 70 | res.status(200).json({ success: true, data: {} }) 71 | }) 72 | 73 | // @desc Get current logged in user 74 | // @route POST /api/v1/auth/me 75 | // @access Private 76 | export const getMe = asyncHandler(async (req: Request, res: Response) => { 77 | const user = req.user 78 | 79 | res.status(200).json({ success: true, data: user }) 80 | }) 81 | 82 | // @desc Update user details 83 | // @route POST /api/v1/auth/updatedetails 84 | // @access Private 85 | export const updateDetails = asyncHandler( 86 | async (req: Request, res: Response) => { 87 | const fieldsToUpdate = { 88 | name: req.body.name, 89 | email: req.body.email.toLowerCase(), 90 | } 91 | const user = await User.findByIdAndUpdate(req.user.id, fieldsToUpdate, { 92 | new: true, 93 | runValidators: true, 94 | context: 'query', 95 | }) 96 | 97 | res.status(200).json({ success: true, data: user }) 98 | } 99 | ) 100 | 101 | // @desc Update password 102 | // @route PUT /api/v1/auth/updatepassword 103 | // @access Private 104 | export const updatePassword = asyncHandler( 105 | async (req: Request, res: Response, next: NextFunction) => { 106 | const user: any = await User.findById(req.user.id).select('+password') 107 | 108 | if (!(await user.matchPassword(req.body.currentPassword))) { 109 | return next(new ErrorResponse('Password is incorrect', 401)) 110 | } 111 | 112 | user.password = req.body.newPassword 113 | await user.save() 114 | 115 | sendTokenResponse(user, 200, res) 116 | } 117 | ) 118 | 119 | // @desc Forgot password 120 | // @route POST /api/v1/auth/forgotpassword 121 | // @access Public 122 | export const forgotPassword = asyncHandler( 123 | async (req: Request, res: Response, next: NextFunction) => { 124 | const user = await User.findOne({ email: req.body.email.toLowerCase() }) 125 | 126 | if (!user) { 127 | return next(new ErrorResponse('There is no user with that email', 404)) 128 | } 129 | 130 | const resetToken = user.getResetPasswordToken() 131 | 132 | await user.save({ validateBeforeSave: false }) 133 | 134 | const resetUrl = `${req.protocol}://${req.get( 135 | 'host' 136 | )}/api/v1/auth/resetpassword/${resetToken}` 137 | 138 | // const message = `You are receiving this email because you (or someone else) has requested the reset of a password. Please make a PUT request to: \n\n ${resetUrl}` 139 | 140 | try { 141 | await sendEmail({ 142 | template: 'forgot-password', 143 | email: user.email, 144 | locals: { 145 | link: resetUrl, 146 | }, 147 | }) 148 | res.status(200).json({ success: true, data: 'Email sent' }) 149 | } catch (err) { 150 | console.log(err) 151 | user.resetPasswordToken = undefined 152 | user.resetPasswordExpire = undefined 153 | 154 | await user.save({ validateBeforeSave: false }) 155 | 156 | return next(new ErrorResponse('Email could not be sent', 500)) 157 | } 158 | } 159 | ) 160 | 161 | // @desc Reset password 162 | // @route PUT /api/v1/auth/resetpassword/:resettoken 163 | // @access Public 164 | export const resetPassword = asyncHandler( 165 | async (req: Request, res: Response, next: NextFunction) => { 166 | // Get hashed token 167 | const resetPasswordToken = crypto 168 | .createHash('sha256') 169 | .update(req.params.resettoken) 170 | .digest('hex') 171 | 172 | console.log(resetPasswordToken) 173 | 174 | const user = await User.findOne({ 175 | resetPasswordToken, 176 | resetPasswordExpire: { $gt: Date.now() }, 177 | }) 178 | 179 | if (!user) { 180 | return next(new ErrorResponse('Invalid token', 400)) 181 | } 182 | 183 | // Set new password 184 | user.password = req.body.password 185 | user.resetPasswordToken = undefined 186 | user.resetPasswordExpire = undefined 187 | await user.save() 188 | 189 | sendTokenResponse(user, 200, res) 190 | } 191 | ) 192 | 193 | // @desc Email verification 194 | // @route PUT /api/v1/auth/emailverification/:resettoken 195 | // @access Public 196 | export const emailVerification = asyncHandler( 197 | async (req: Request, res: Response, next: NextFunction) => { 198 | // Get hashed token 199 | const emailVerificationToken = crypto 200 | .createHash('sha256') 201 | .update(req.params.resettoken) 202 | .digest('hex') 203 | 204 | console.log(emailVerificationToken) 205 | 206 | const user = await User.findOne({ 207 | emailVerificationToken, 208 | emailVerificationExpire: { $gt: Date.now() }, 209 | }) 210 | 211 | if (!user) { 212 | return next(new ErrorResponse('Email verification link has expired', 400)) 213 | } 214 | 215 | user.isEmailVerified = true 216 | user.emailVerificationToken = undefined 217 | user.emailVerificationExpire = undefined 218 | await user.save() 219 | 220 | res.status(200).json({ success: true, data: user }) 221 | } 222 | ) 223 | 224 | // @desc Send Email Verification 225 | // @route POST /api/v1/auth/sendemailverification 226 | // @access Public 227 | export const postSendEmailVerification = asyncHandler( 228 | async (req: Request, res: Response, next: NextFunction) => { 229 | if (!req.body.email) { 230 | return next(new ErrorResponse('Email is required', 400)) 231 | } 232 | 233 | const user = await User.findOne({ 234 | email: req.body.email, 235 | isEmailVerified: false, 236 | }) 237 | 238 | if (!user) { 239 | return next( 240 | new ErrorResponse( 241 | 'User already verified or No user with that email address', 242 | 400 243 | ) 244 | ) 245 | } 246 | 247 | const isSent = sendEmailVerification(user, req) 248 | isSent.then((data) => { 249 | if (!data) { 250 | return next(new ErrorResponse('Email could not be sent', 500)) 251 | } 252 | return res.status(200).json({ success: true, data: 'Email sent' }) 253 | }) 254 | } 255 | ) 256 | 257 | // Get token from model, create cookie and send response 258 | const sendTokenResponse = (user: IUser, statusCode: number, res: Response) => { 259 | const token = user.getSignedJwtToken() 260 | 261 | const options = { 262 | expires: new Date( 263 | Date.now() + +process.env.JWT_COOKIE_EXPIRE! * 24 * 60 * 60 * 1000 264 | ), 265 | httpOnly: true, 266 | secure: false, 267 | } 268 | 269 | if (process.env.NODE_ENV === 'production') { 270 | options.secure = true 271 | } 272 | 273 | res 274 | .status(statusCode) 275 | .cookie('token', token, options) 276 | .json({ success: true, token }) 277 | } 278 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/src/components/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import { NextFunction, Request, Response } from 'express' 3 | 4 | import { get, controller, post, use, put } from '../../decorators' 5 | import ErrorResponse from '../../utils/errorResponse' 6 | import sendEmail from '../../utils/sendEmail' 7 | import sendEmailVerification from '../../utils/sendEmailVerification' 8 | 9 | import User, { IUser } from '../users/user.model' 10 | import { protect } from './auth.middleware' 11 | 12 | @controller('/api/v1/auth') 13 | // @ts-ignore 14 | class AuthController { 15 | // @desc Register user 16 | // @route POST /api/v1/auth/register 17 | // @access Public 18 | @post('/register') 19 | async register(req: Request, res: Response) { 20 | let { name, email, password, role } = req.body 21 | 22 | email = email.toLowerCase() 23 | 24 | const user = await User.create({ 25 | name, 26 | email, 27 | password, 28 | role, 29 | }) 30 | 31 | sendEmailVerification(user, req) 32 | 33 | sendTokenResponse(user, 200, res) 34 | } 35 | 36 | // @desc Login user 37 | // @route POST /api/v1/auth/login 38 | // @access Public 39 | @post('/login') 40 | async login(req: Request, res: Response, next: NextFunction) { 41 | let { email, password } = req.body 42 | 43 | if (!email || !password) { 44 | return next( 45 | new ErrorResponse('Please provide an email and password', 400) 46 | ) 47 | } 48 | 49 | email = email.toLowerCase() 50 | 51 | const user = await User.findOne({ email }).select('+password') 52 | 53 | if (!user) { 54 | return next(new ErrorResponse('Invalid credentials', 400)) 55 | } 56 | 57 | const isMatch = await user.matchPassword(password) 58 | 59 | if (!isMatch) { 60 | return next(new ErrorResponse('Invalid credentials', 400)) 61 | } 62 | 63 | sendTokenResponse(user, 200, res) 64 | } 65 | 66 | // @desc Log user out / clear cookie 67 | // @route GET /api/v1/auth/logout 68 | // @access Private 69 | @get('/logout') 70 | async logout(_: Request, res: Response) { 71 | res.cookie('token', 'none', { 72 | expires: new Date(Date.now() + 10 * 1000), 73 | httpOnly: true, 74 | }) 75 | 76 | res.status(200).json({ success: true, data: {} }) 77 | } 78 | 79 | // @desc Get current logged in user 80 | // @route POST /api/v1/auth/me 81 | // @access Private 82 | @post('/me') 83 | @use(protect) 84 | async getMe(req: Request, res: Response) { 85 | const user = req.user 86 | 87 | res.status(200).json({ success: true, data: user }) 88 | } 89 | 90 | // @desc Update user details 91 | // @route POST /api/v1/auth/updatedetails 92 | // @access Private 93 | @put('/updatedetails') 94 | @use(protect) 95 | async updateDetails(req: Request, res: Response) { 96 | const fieldsToUpdate = { 97 | name: req.body.name, 98 | email: req.body.email.toLowerCase(), 99 | } 100 | const user = await User.findByIdAndUpdate(req.user.id, fieldsToUpdate, { 101 | new: true, 102 | runValidators: true, 103 | context: 'query', 104 | }) 105 | 106 | res.status(200).json({ success: true, data: user }) 107 | } 108 | 109 | // @desc Update password 110 | // @route PUT /api/v1/auth/updatepassword 111 | // @access Private 112 | @put('/updatepassword') 113 | @use(protect) 114 | async updatePassword(req: Request, res: Response, next: NextFunction) { 115 | const user: any = await User.findById(req.user.id).select('+password') 116 | 117 | if (!(await user.matchPassword(req.body.currentPassword))) { 118 | return next(new ErrorResponse('Password is incorrect', 401)) 119 | } 120 | 121 | user.password = req.body.newPassword 122 | await user.save() 123 | 124 | sendTokenResponse(user, 200, res) 125 | } 126 | 127 | // @desc Forgot password 128 | // @route POST /api/v1/auth/forgotpassword 129 | // @access Public 130 | @post('/forgotpassword') 131 | async forgotPassword(req: Request, res: Response, next: NextFunction) { 132 | const user = await User.findOne({ email: req.body.email.toLowerCase() }) 133 | 134 | if (!user) { 135 | return next(new ErrorResponse('There is no user with that email', 404)) 136 | } 137 | 138 | const resetToken = user.getResetPasswordToken() 139 | 140 | await user.save({ validateBeforeSave: false }) 141 | 142 | const resetUrl = `${req.protocol}://${req.get( 143 | 'host' 144 | )}/api/v1/auth/resetpassword/${resetToken}` 145 | 146 | // const message = `You are receiving this email because you (or someone else) has requested the reset of a password. Please make a PUT request to: \n\n ${resetUrl}` 147 | 148 | try { 149 | await sendEmail({ 150 | template: 'forgot-password', 151 | email: user.email, 152 | locals: { 153 | link: resetUrl, 154 | }, 155 | }) 156 | res.status(200).json({ success: true, data: 'Email sent' }) 157 | } catch (err) { 158 | console.log(err) 159 | user.resetPasswordToken = undefined 160 | user.resetPasswordExpire = undefined 161 | 162 | await user.save({ validateBeforeSave: false }) 163 | 164 | return next(new ErrorResponse('Email could not be sent', 500)) 165 | } 166 | } 167 | 168 | // @desc Reset password 169 | // @route PUT /api/v1/auth/resetpassword/:resettoken 170 | // @access Public 171 | @put('/resetpassword/:resettoken') 172 | async resetPassword(req: Request, res: Response, next: NextFunction) { 173 | // Get hashed token 174 | const resetPasswordToken = crypto 175 | .createHash('sha256') 176 | .update(req.params.resettoken) 177 | .digest('hex') 178 | 179 | console.log(resetPasswordToken) 180 | 181 | const user = await User.findOne({ 182 | resetPasswordToken, 183 | resetPasswordExpire: { $gt: Date.now() }, 184 | }) 185 | 186 | if (!user) { 187 | return next(new ErrorResponse('Invalid token', 400)) 188 | } 189 | 190 | // Set new password 191 | user.password = req.body.password 192 | user.resetPasswordToken = undefined 193 | user.resetPasswordExpire = undefined 194 | await user.save() 195 | 196 | sendTokenResponse(user, 200, res) 197 | } 198 | 199 | // @desc Email verification 200 | // @route PUT /api/v1/auth/emailverification/:resettoken 201 | // @access Public 202 | @put('/emailverification/:resettoken') 203 | async emailVerification(req: Request, res: Response, next: NextFunction) { 204 | // Get hashed token 205 | const emailVerificationToken = crypto 206 | .createHash('sha256') 207 | .update(req.params.resettoken) 208 | .digest('hex') 209 | 210 | console.log(emailVerificationToken) 211 | 212 | const user = await User.findOne({ 213 | emailVerificationToken, 214 | emailVerificationExpire: { $gt: Date.now() }, 215 | }) 216 | 217 | if (!user) { 218 | return next(new ErrorResponse('Email verification link has expired', 400)) 219 | } 220 | 221 | user.isEmailVerified = true 222 | user.emailVerificationToken = undefined 223 | user.emailVerificationExpire = undefined 224 | await user.save() 225 | 226 | res.status(200).json({ success: true, data: user }) 227 | } 228 | 229 | // @desc Send Email Verification 230 | // @route POST /api/v1/auth/sendemailverification 231 | // @access Public 232 | @post('/sendemailverification') 233 | async postSendEmailVerification( 234 | req: Request, 235 | res: Response, 236 | next: NextFunction 237 | ) { 238 | if (!req.body.email) { 239 | return next(new ErrorResponse('Email is required', 400)) 240 | } 241 | 242 | const user = await User.findOne({ 243 | email: req.body.email, 244 | isEmailVerified: false, 245 | }) 246 | 247 | if (!user) { 248 | return next( 249 | new ErrorResponse( 250 | 'User already verified or No user with that email address', 251 | 400 252 | ) 253 | ) 254 | } 255 | 256 | const isSent = sendEmailVerification(user, req) 257 | isSent.then((data) => { 258 | if (!data) { 259 | return next(new ErrorResponse('Email could not be sent', 500)) 260 | } 261 | return res.status(200).json({ success: true, data: 'Email sent' }) 262 | }) 263 | } 264 | } 265 | 266 | // Get token from model, create cookie and send response 267 | const sendTokenResponse = (user: IUser, statusCode: number, res: Response) => { 268 | const token = user.getSignedJwtToken() 269 | 270 | const options = { 271 | expires: new Date( 272 | Date.now() + +process.env.JWT_COOKIE_EXPIRE! * 24 * 60 * 60 * 1000 273 | ), 274 | httpOnly: true, 275 | secure: false, 276 | } 277 | 278 | if (process.env.NODE_ENV === 'production') { 279 | options.secure = true 280 | } 281 | 282 | res 283 | .status(statusCode) 284 | .cookie('token', token, options) 285 | .json({ success: true, token }) 286 | } 287 | -------------------------------------------------------------------------------- /advance-typescript-with-decorators/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, 18 | "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | "rootDir": "./src" /* Specify the root folder within your source files. */, 30 | "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | "typeRoots": [ 35 | "./src/types" 36 | ] /* Specify multiple folders that act like './node_modules/@types'. */, 37 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 40 | // "resolveJsonModule": true, /* Enable importing .json files. */ 41 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 42 | 43 | /* JavaScript Support */ 44 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 45 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 46 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 47 | 48 | /* Emit */ 49 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 50 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 51 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 52 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 53 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 54 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 55 | "removeComments": true /* Disable emitting comments. */, 56 | // "noEmit": true, /* Disable emitting files from a compilation. */ 57 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 58 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 59 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 60 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 61 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 62 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 63 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 64 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 65 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 66 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 67 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 68 | "noEmitOnError": true /* Disable emitting files if any type checking errors are reported. */, 69 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 70 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 71 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 72 | 73 | /* Interop Constraints */ 74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 79 | 80 | /* Type Checking */ 81 | "strict": true /* Enable all strict type-checking options. */, 82 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 83 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 88 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 | "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, 91 | "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, 92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 | "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */, 94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 96 | "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, 97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 | "allowUnreachableCode": true /* Disable error reporting for unreachable code. */, 100 | 101 | /* Completeness */ 102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /basic-typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs" /* Specify what module code is generated. */, 29 | "rootDir": "./src" /* Specify the root folder within your source files. */, 30 | "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | "typeRoots": [ 35 | "./src/types" 36 | ] /* Specify multiple folders that act like './node_modules/@types'. */, 37 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 40 | // "resolveJsonModule": true, /* Enable importing .json files. */ 41 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 42 | 43 | /* JavaScript Support */ 44 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 45 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 46 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 47 | 48 | /* Emit */ 49 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 50 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 51 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 52 | "sourceMap": true /* Create source map files for emitted JavaScript files. */, 53 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 54 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 55 | "removeComments": true /* Disable emitting comments. */, 56 | // "noEmit": true, /* Disable emitting files from a compilation. */ 57 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 58 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 59 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 60 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 61 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 62 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 63 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 64 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 65 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 66 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 67 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 68 | "noEmitOnError": true /* Disable emitting files if any type checking errors are reported. */, 69 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 70 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 71 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 72 | 73 | /* Interop Constraints */ 74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 79 | 80 | /* Type Checking */ 81 | "strict": true /* Enable all strict type-checking options. */, 82 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 83 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 88 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 | "noUnusedLocals": true /* Enable error reporting when local variables aren't read. */, 91 | "noUnusedParameters": true /* Raise an error when a function parameter isn't read. */, 92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 | "noImplicitReturns": true /* Enable error reporting for codepaths that do not explicitly return in a function. */, 94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 96 | "noImplicitOverride": true /* Ensure overriding members in derived classes are marked with an override modifier. */, 97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 | "allowUnreachableCode": true /* Disable error reporting for unreachable code. */, 100 | 101 | /* Completeness */ 102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 | } 105 | } 106 | --------------------------------------------------------------------------------