├── .gitignore ├── config └── mongoConfig.js ├── package.json ├── middleware └── authMiddleware.js ├── models ├── usersSchema.js └── blogsSchema.js ├── server.js ├── routes ├── authRouter.js ├── usersRouter.js └── blogsRouter.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | node_modules/ 3 | .env -------------------------------------------------------------------------------- /config/mongoConfig.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | module.exports = async () => { 4 | try { 5 | await mongoose.connect(process.env.MONGODB_URI) 6 | mongoose.connection 7 | console.log('MongoDB Connected'); 8 | } catch (error) { 9 | console.error(error) 10 | 11 | } 12 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog_api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcrypt": "^5.0.1", 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.0.1", 16 | "express": "^4.18.1", 17 | "express-validator": "^6.14.1", 18 | "helmet": "^5.1.0", 19 | "jsonwebtoken": "^8.5.1", 20 | "mongoose": "^6.3.7", 21 | "morgan": "^1.10.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | 3 | module.exports = (req, res, next) => { 4 | //get the token from the headers object 5 | const token = req.header('x-auth-token') 6 | // iF 7 | if(!token){ 8 | return res.json('No Token Access Denied') 9 | } 10 | try { 11 | const decoded = jwt.verify(token, process.env.SECRET_KEY) 12 | req.user = decoded 13 | console.log(decoded) 14 | next() 15 | } catch (error) { 16 | // console.log(error); 17 | res.status(400).json('Token not valid') 18 | } 19 | } -------------------------------------------------------------------------------- /models/usersSchema.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const userSchema = mongoose.Schema({ 3 | username: { 4 | type:String, 5 | required: true 6 | }, 7 | email:{ 8 | type: String, 9 | required: true, 10 | unique: true 11 | 12 | }, 13 | 14 | birthday: { 15 | type: Date, 16 | // required: true 17 | }, 18 | 19 | age: { 20 | type: Number 21 | }, 22 | 23 | password:{ 24 | type: String, 25 | required: true 26 | } 27 | }) 28 | 29 | module.exports = mongoose.model('User', userSchema) -------------------------------------------------------------------------------- /models/blogsSchema.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const blogSchema = mongoose.Schema({ 4 | created_by: { 5 | type: String, 6 | required: true 7 | }, 8 | 9 | created_at: { 10 | type: Date, 11 | default: Date.now 12 | }, 13 | 14 | blogs_title:{ 15 | type: String, 16 | required: true 17 | }, 18 | 19 | blogs_content:{ 20 | type: String, 21 | required: true 22 | }, 23 | 24 | private: { 25 | type: Boolean, 26 | default: false, 27 | // required: true 28 | }, 29 | 30 | user: { 31 | type: mongoose.Schema.Types.ObjectId, 32 | ref: 'User' 33 | } 34 | 35 | 36 | }) 37 | 38 | module.exports = mongoose.model('Blogs', blogSchema) -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const mongoConfig = require('./config/mongoConfig') 3 | const blogsRouter = require('./routes/blogsRouter') 4 | const usersRouter = require('./routes/usersRouter') 5 | const authRouter = require('./routes/authRouter') 6 | require('dotenv').config() 7 | const morgan = require('morgan') 8 | const helmet = require('helmet') 9 | const cors = require('cors') 10 | 11 | const app = express() 12 | const PORT = process.env.PORT || 5000 13 | 14 | //Middleware 15 | app.use(express.json()) 16 | app.use(morgan('dev')) 17 | app.use(helmet()) 18 | app.use(cors()) 19 | 20 | //Routers 21 | app.use('/auth', authRouter) 22 | app.use('/blogs', blogsRouter) 23 | app.use('/users', usersRouter) 24 | 25 | app.get('/', (req, res) => { 26 | res.status(200).json({message: "Welcome"}) 27 | }) 28 | 29 | app.listen(PORT, () => { 30 | console.log(`Server is running on port: ${PORT}`) 31 | mongoConfig() 32 | }) -------------------------------------------------------------------------------- /routes/authRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const{check, validationResult} = require('express-validator') 3 | const bcrypt = require('bcrypt') 4 | const jwt = require('jsonwebtoken') 5 | const UserModel = require('../models/usersSchema') 6 | 7 | const router = express.Router() 8 | 9 | //User Login 10 | 11 | router.post('/',[ 12 | check("email","Please provide a valid email").isEmail(), 13 | check("password", "Check your password!").notEmpty() 14 | ], async (req, res) => { 15 | const userData = req.body 16 | 17 | const errors = validationResult(req) 18 | //Checks for validation errors 19 | if(!errors.isEmpty()){ 20 | return res.json(errors.array()) 21 | } 22 | try { 23 | //Find the user with the provided email 24 | const user = await UserModel.findOne({email: userData.email}) 25 | if (!user){ 26 | return res.json('User not found!') 27 | } 28 | 29 | // Compare the plain text password to hashed password 30 | const isMatch = await bcrypt.compare(userData.password, user.password) 31 | if(!isMatch){ 32 | return res.json('Password is not a match') 33 | } 34 | 35 | //Create a new JWT Token 36 | const payload = { 37 | id: user._id, 38 | email: user.email 39 | } 40 | 41 | const TOKEN = jwt.sign(payload, process.env.SECRET_KEY) 42 | 43 | res.status(201).json({ 44 | user:user, 45 | token:TOKEN 46 | }) 47 | 48 | } catch(error) { 49 | console.log(error) 50 | res.status(500).json('Server Error') 51 | } 52 | }) 53 | 54 | 55 | 56 | module.exports = router -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BLOG_API 2 | 3 | ## Description 4 | This is a blog API with full Create, Read, Update, and Delete(CRUD) functionality. 5 | 6 | ### Tech Stack 7 | 8 | - Server: Node.js, Express.js 9 | - Database: MongoDB 10 | - Frontend: Postman 11 | - Deploy: Heroku 12 | 13 | ### ENVIONMENT VARIABLES 14 | To run this API the following environment vaariables are required: 15 | 16 | - MONGODB_URI 17 | - SECRET_KEY 18 | 19 | ### RUN LOCALLY 20 | Clone the project and follow all steps and have all dependencies to run the project locally. 21 | 22 | git clone https://github.com/fmdavis1/blog_api 23 | 24 | ### Install dependencies 25 | 26 | npm init -y 27 | 28 | npm i: 29 | 30 | - bcrypt 31 | - dotenv 32 | - express 33 | - helmet 34 | - jsonwebtoken 35 | - mongoose 36 | - morgan 37 | 38 | ### ROUTES 39 | - Endpoints, Parameters, Schema 40 | 41 | ### Endpoints 42 | 43 | - To run use Port 5000 as follows: http://localhost:5000 and add endpoints 44 | 45 | - server app.get('/')return message "Server is running" 46 | 47 | - usersRouter.post('/auth'): Creates Login.Login with username and password only. Token is sent to header for furher access. 48 | - /auth 49 | - Login 50 | - CREATE 51 | 52 | - usersRouter.post('/users')Registers users, usersSchema is used. Password is hashed. Token sent to headers for further access. 53 | - /users 54 | - CREATE 55 | - Registration 56 | - READ 57 | - Get users 58 | - UPDATE 59 | - Update user 60 | - DELETE 61 | - Delete user 62 | 63 | 64 | Blog('/blogs') 65 | router.get('/blogs'): all public blog and private blogs are included, must be logged in and have a token. 66 | 67 | router.post('/blogs'): Creates Blog with blogsSchema. Must have a token 68 | 69 | router.put('/blogs/:id'): Updates blog with id, parameter(String) id is needed to update blog. Must have a token 70 | 71 | router.get('/blogs/:id'): Returns blog associcated to id, parameter(String) id is required to find blog. Only blog associated with id appears. Must have a token 72 | 73 | router.get('/blogs/private'): Returns blog associcated to private. Onlyprivate blogs appear where the value to the key of private in the blogsSchema is set to true. 74 | 75 | router.delete('/blogs/:id): Deletes blog associated with id, need a token for Authorization and parameter(string) id is required to delete blog 76 | 77 | 78 | - /blogs 79 | - full CRUD 80 | - CREATE 81 | -Create blog 82 | - READ 83 | - Get blogs 84 | - Get private blogs 85 | - READ BY ID 86 | - Get blog by id 87 | - UPDATE 88 | - Update blog 89 | - DELETE 90 | -Delete blog 91 | 92 | ### SCHEMAS 93 | 94 | ### Blog Schema: 95 | - username: type: String, required: true 96 | - created_by: type: String, required: true, 97 | - created_at:type:String, required:true, 98 | - blog_title:type:String, required: true 99 | - private:type:Boolean,default false 100 | 101 | ### User Schema: 102 | - username:type:String,required:true, 103 | - email:type:String,required:true, 104 | - birthday:date,required:true, 105 | - age:number, 106 | - password:String:required:true 107 | 108 | ### Deploy 109 | - Deployed to Heroku -------------------------------------------------------------------------------- /routes/usersRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const UserModel = require('../models/usersSchema') 3 | //pulls out the two functions we need from express validaor 4 | const {check, validationResult} = require('express-validator') 5 | const bcrypt = require('bcrypt') 6 | const jwt = require('jsonwebtoken') 7 | 8 | //Create a Router 9 | const router = express.Router() 10 | 11 | //Get Users 12 | router.get('/', async (req, res) => { 13 | try { 14 | const users = await UserModel.find() 15 | res.status(200).json(users) 16 | } catch (error) { 17 | console.log(error) 18 | 19 | } 20 | }) 21 | 22 | //Create or Register a new User 23 | router.post('/',[ 24 | check('username', "Username is required from Middleware!").notEmpty(), 25 | check("email", "Please use a valid email from middleware").isEmail(), 26 | check("password","Please enter a password").notEmpty(), 27 | check("password","Please enter a password with six or more characters.").isLength({min: 6}), 28 | ], async (req, res) => { 29 | const userData = req.body 30 | 31 | //Checks for validation errors 32 | const errors = validationResult(req) 33 | if(!errors.isEmpty()){ 34 | return res.json(errors.array()) 35 | } 36 | //Write the user to the db 37 | try { 38 | //checking if there is a user with this email in the db 39 | const userExist = await UserModel.findOne({email: userData.email}) 40 | //if user exist we return! 41 | if(userExist){ 42 | return res.json({msd: "User already exists!"}) 43 | } 44 | 45 | //*========Create a New User 46 | // 1 Create the salt 47 | const SALT = await bcrypt.genSalt(12) 48 | // 2 Use the salt to creat a hash with the user's password 49 | const hashedPassword = await bcrypt.hash(userData.password, SALT) 50 | // 3 Assign the hashed password to the userData 51 | userData.password = hashedPassword 52 | //Write the user to the db 53 | const user = await UserModel.create(userData) 54 | 55 | //create a new JWT Token 56 | 57 | const payload = { 58 | id: user._id, 59 | email: user.email 60 | } 61 | const TOKEN = jwt.sign(payload, process.env.SECRET_KEY) 62 | 63 | res.status(201).json({ 64 | user:user, 65 | token:TOKEN 66 | }) 67 | } catch (error) { 68 | console.log(error) 69 | res.status(400).json('Bad request!!!') 70 | 71 | } 72 | 73 | }) 74 | 75 | //Update User By Id 76 | router.put('/:id', async(req, res) => { 77 | const id = req.params.id 78 | newUsersData = req.body 79 | try { 80 | //find the user by id 81 | const user = await UserModel.findByIdAndUpdate(id, newUsersData, {new: true}) 82 | res.status(202).json(user) 83 | console.log(user) 84 | } catch (error){ 85 | console.log(error) 86 | 87 | } 88 | 89 | }) 90 | 91 | //Delete a User 92 | router.delete('/:id', async(req, res) => { 93 | const id = req.params.id 94 | console.log('FROM DELETE', req.user) 95 | try{ 96 | //first we find the user we're going to delete 97 | const user = await UserModel.findByIdAndDelete(id) 98 | res.status(200).json({msg:'user was deleted!'}) 99 | 100 | }catch(error){ 101 | console.log(error) 102 | } 103 | }) 104 | 105 | module.exports = router -------------------------------------------------------------------------------- /routes/blogsRouter.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const BlogsModel = require('../models/blogsSchema') 3 | const authMiddleware = require('../middleware/authMiddleware') 4 | 5 | 6 | //Create a Router 7 | const router = express.Router() 8 | 9 | //Get Blogs 10 | router.get('/',authMiddleware, async(req, res) => { 11 | try { 12 | const blogs = await BlogsModel.find() 13 | 14 | res.status(200).json(blogs) 15 | } catch (error) { 16 | console.log(error) 17 | 18 | } 19 | }) 20 | router.get('/private',authMiddleware, async(req, res) => { 21 | try { 22 | const blogs = await BlogsModel.find({private: true}) 23 | 24 | res.status(200).json(blogs) 25 | } catch (error) { 26 | console.log(error) 27 | 28 | } 29 | }) 30 | 31 | //Create Blogs 32 | router.post('/', authMiddleware, async (req, res) => { 33 | const blogsData = req.body //gets the data from the request 34 | blogsData.user = req.user.id 35 | console.log(blogsData); 36 | try { 37 | const blog = await BlogsModel.create(blogsData) 38 | //create the blog in the db 39 | //send back the response 40 | res.status(201).json(blog) 41 | } catch (error) { 42 | console.error(error) 43 | res.status(400).json('Bad request!!!!') 44 | 45 | } 46 | 47 | }) 48 | 49 | //Get Blog by ID 50 | 51 | router.get('/:id',authMiddleware, async(req, res) => { 52 | const id = req.params.id 53 | console.log(id) 54 | 55 | try { 56 | const blog = await BlogsModel.findById(id) 57 | res.status(200).json(blog) 58 | } catch (error) { 59 | console.error(error) 60 | res.status(400).json({ 61 | msg: 'Id not found' 62 | }) 63 | 64 | } 65 | }) 66 | 67 | //Update Blog By ID 68 | router.put('/:id', authMiddleware, async(req, res) => { 69 | const id = req.params.id 70 | const newBlogsData = req.body 71 | try { 72 | //find the blog by id 73 | const blog = await BlogsModel.findByIdAndUpdate(id, newBlogsData, {new: true}) 74 | res.status(202).json(blog) 75 | } catch (error) { 76 | console.log(error) 77 | 78 | } 79 | }) 80 | 81 | //Delete a Blog 82 | router.delete('/:id', authMiddleware, async (req, res) => { 83 | const id = req.params.id 84 | console.log('FROM DELETE', req.user) 85 | try { 86 | //first we find the blog we're going to delete 87 | const blogToDelete = await BlogsModel.findById(id) 88 | console.log(blogToDelete) 89 | console.log(blogToDelete.user._id.toString(),'||', req.user.id) 90 | //Here we check that the user who created the blog is the one asking to delete the blog 91 | //By checing the ID's 92 | 93 | if(blogToDelete.user._id.toString() !== req.user.id){ 94 | //if they are NOT the same we send error message 95 | return res.status(400).json({msg:'Not Authorized!'}) 96 | } 97 | 98 | // if they are the same ID's we delete it const blog 99 | const blog = await BlogsModel.findByIdAndDelete(id) 100 | res.status(200).json({msg:'Blog was deleted!'}) 101 | } catch (error) { 102 | console.log(error) 103 | } 104 | 105 | }) 106 | 107 | module.exports = router 108 | 109 | 110 | --------------------------------------------------------------------------------