├── .gitignore ├── docker-compose.yml ├── package.json └── src ├── config └── config.js ├── controllers └── user.controller.js ├── index.js ├── models ├── index.js └── rest │ └── User.js ├── routes ├── index.js └── user.js ├── server.js └── utils └── isAuthenticated.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | access.log 4 | package-lock.json -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | db: 5 | image: postgres 6 | container_name: postgres 7 | restart: always 8 | environment: 9 | POSTGRES_PASSWORD: postgres 10 | POSTGRES_USER: postgres 11 | POSTGRES_DB: rest 12 | ports: 13 | - 5432:5432 14 | volumes: 15 | - restpsql:/var/lib/postgresql/data 16 | 17 | adminer: 18 | image: adminer 19 | restart: always 20 | ports: 21 | - 8080:8080 22 | 23 | volumes: 24 | restpsql: 25 | external: true -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-api-30-mins-video", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "env NODE_ENV=development nodemon src/index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^8.2.0", 13 | "esm": "^3.2.25", 14 | "express": "^4.17.1", 15 | "helmet": "^4.2.0", 16 | "morgan": "^1.10.0", 17 | "pg": "^8.5.1", 18 | "sequelize": "^5.21.7" 19 | }, 20 | "devDependencies": { 21 | "nodemon": "^2.0.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv/config'); 2 | module.exports = { 3 | development: { 4 | databases: { 5 | rest: { 6 | database: process.env.POSTGRES_DB, 7 | username: process.env.POSTGRES_USER, 8 | password: process.env.POSTGRES_PASS, 9 | host: process.env.POSTGRES_HOST, 10 | port: process.env.POSTGRES_PORT, 11 | dialect: 'postgres', 12 | }, 13 | }, 14 | }, 15 | production: { 16 | databases: { 17 | rest: { 18 | database: process.env.POSTGRES_DB, 19 | username: process.env.POSTGRES_USER, 20 | password: process.env.POSTGRES_PASS, 21 | host: process.env.POSTGRES_HOST, 22 | port: process.env.POSTGRES_PORT, 23 | dialect: 'postgres', 24 | }, 25 | }, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const db = require('../models'); 2 | const User = db.rest.models.user; 3 | 4 | exports.getUser = async (req, res) => { 5 | const { id } = req.params; 6 | 7 | const user = await User.findOne({ 8 | where: { 9 | id, 10 | }, 11 | }); 12 | 13 | if (!user) { 14 | return res.status(400).send({ 15 | message: `No user found with the id ${id}`, 16 | }); 17 | } 18 | 19 | return res.send(user); 20 | }; 21 | 22 | exports.createUser = async (req, res) => { 23 | const { username, password } = req.body; 24 | if (!username || !password) { 25 | return res.status(400).send({ 26 | message: 'Please provide a username and a password to create a user!', 27 | }); 28 | } 29 | 30 | let usernameExists = await User.findOne({ 31 | where: { 32 | username, 33 | }, 34 | }); 35 | 36 | if (usernameExists) { 37 | return res.status(400).send({ 38 | message: 'An account with that username already exists!', 39 | }); 40 | } 41 | 42 | try { 43 | let newUser = await User.create({ 44 | username, 45 | password, 46 | }); 47 | return res.send(newUser); 48 | } catch (err) { 49 | return res.status(500).send({ 50 | message: `Error: ${err.message}`, 51 | }); 52 | } 53 | }; 54 | 55 | exports.deleteUser = async (req, res) => { 56 | const { id } = req.body; 57 | if (!id) { 58 | return res.status(400).send({ 59 | message: 'Please provide a id for the user you are trying to delete!', 60 | }); 61 | } 62 | 63 | const user = await User.findOne({ 64 | where: { 65 | id, 66 | }, 67 | }); 68 | 69 | if (!user) { 70 | return res.status(400).send({ 71 | message: `No user found with the id ${id}`, 72 | }); 73 | } 74 | 75 | try { 76 | await user.destroy(); 77 | return res.send({ 78 | message: `User ${id} has been deleted!`, 79 | }); 80 | } catch (err) { 81 | return res.status(500).send({ 82 | message: `Error: ${err.message}`, 83 | }); 84 | } 85 | }; 86 | 87 | exports.updateUser = async (req, res) => { 88 | const { username, password } = req.body; 89 | const { id } = req.params; 90 | 91 | const user = await User.findOne({ 92 | where: { 93 | id, 94 | }, 95 | }); 96 | 97 | if (!user) { 98 | return res.status(400).send({ 99 | message: `No user found with the id ${id}`, 100 | }); 101 | } 102 | 103 | try { 104 | if (username) { 105 | user.username = username; 106 | } 107 | if (password) { 108 | user.password = password; 109 | } 110 | 111 | user.save(); 112 | return res.send({ 113 | message: `User ${id} has been updated!`, 114 | }); 115 | } catch (err) { 116 | return res.status(500).send({ 117 | message: `Error: ${err.message}`, 118 | }); 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require = require('esm')(module); 2 | module.exports = require('./server.js'); 3 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const Sequelize = require('sequelize'); 5 | const basename = path.basename(__filename); 6 | const env = process.env.NODE_ENV || 'development'; 7 | const config = require(__dirname + '/../config/config.js')[env]; 8 | 9 | let db = {}; 10 | 11 | const databases = Object.keys(config.databases); 12 | 13 | for (let i = 0; i < databases.length; i++) { 14 | let database = databases[i]; 15 | let dbPath = config.databases[database]; 16 | db[database] = new Sequelize( 17 | dbPath.database, 18 | dbPath.username, 19 | dbPath.password, 20 | dbPath 21 | ); 22 | } 23 | 24 | /**Add the Database Models**/ 25 | fs.readdirSync(__dirname + '/rest') 26 | .filter((file) => { 27 | return ( 28 | file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js' 29 | ); 30 | }) 31 | .forEach((file) => { 32 | var model = db.rest.import(path.join(__dirname + '/rest', file)); 33 | db[model.name] = model; 34 | }); 35 | 36 | Object.keys(db).forEach((modelName) => { 37 | if (db[modelName].associate) { 38 | db[modelName].associate(db); 39 | } 40 | }); 41 | 42 | module.exports = db; 43 | -------------------------------------------------------------------------------- /src/models/rest/User.js: -------------------------------------------------------------------------------- 1 | const user = (sequelize, DataTypes) => { 2 | const User = sequelize.define( 3 | 'user', 4 | { 5 | id: { 6 | type: DataTypes.INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true, 9 | }, 10 | username: { 11 | type: DataTypes.STRING, 12 | unique: true, 13 | }, 14 | password: { 15 | type: DataTypes.STRING, 16 | }, 17 | }, 18 | { 19 | timestamps: true, 20 | freezeTableName: true, 21 | } 22 | ); 23 | 24 | User.sync(); 25 | return User; 26 | }; 27 | 28 | export default user; 29 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import user from './user'; 2 | 3 | export default { 4 | user, 5 | }; 6 | -------------------------------------------------------------------------------- /src/routes/user.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express'; 2 | const user = require('../controllers/user.controller'); 3 | const router = Router(); 4 | 5 | router.get('/:id', user.getUser); 6 | 7 | router.post('/createUser', user.createUser); 8 | 9 | router.post('/delete', user.deleteUser); 10 | 11 | router.post('/update/:id', user.updateUser); 12 | 13 | export default router; 14 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import express from 'express'; 3 | import morgan from 'morgan'; 4 | import helmet from 'helmet'; 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import routes from './routes'; 8 | import { isAuthenticated } from './utils/isAuthenticated'; 9 | 10 | const app = express(); 11 | 12 | const accessLogStream = fs.createWriteStream( 13 | path.join(__dirname, '../access.log'), 14 | { flags: 'a' } 15 | ); 16 | 17 | app.use(helmet()); 18 | app.use(morgan('combined', { stream: accessLogStream })); 19 | app.use(express.json({ limit: '50mb' })); 20 | app.use(express.urlencoded({ extended: true, limit: '50mb' })); 21 | 22 | app.use('/user', isAuthenticated, routes.user); 23 | 24 | app.use((req, res) => { 25 | res.status(404).send('404: Page not found'); 26 | }); 27 | 28 | app.listen(4002, () => { 29 | console.log(`Example app listening on port 4002!`); 30 | }); 31 | -------------------------------------------------------------------------------- /src/utils/isAuthenticated.js: -------------------------------------------------------------------------------- 1 | require('dotenv/config'); 2 | 3 | export const isAuthenticated = async (req, res, next) => { 4 | if (!req.query.apiKey) { 5 | return res.status(401).send({ 6 | message: 'Must be authenticated with an API Key to hit this endpoint', 7 | }); 8 | } else { 9 | const { apiKey } = req.query; 10 | if (apiKey === '123546FGFG4567DSDF4646F') { 11 | return next(); 12 | } else { 13 | return res.status(401).send({ 14 | message: 'API Key does not match!', 15 | }); 16 | } 17 | } 18 | }; 19 | --------------------------------------------------------------------------------