├── .gitignore ├── models ├── User.js └── Recipe.js ├── package.json ├── controllers ├── recipeController.js ├── authMiddleware.js └── userController.js ├── index.js ├── README.md └── routes ├── users.js └── recipes.js /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | -------------------------------------------------------------------------------- /models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | username: { 6 | type: String, 7 | required: true, 8 | 9 | }, 10 | 11 | password: { 12 | type: String, 13 | required: true, 14 | 15 | }, 16 | email:{ 17 | type: String, 18 | required: true, 19 | 20 | } 21 | } 22 | ) 23 | const User = new mongoose.model('User', userSchema); 24 | export default User; -------------------------------------------------------------------------------- /models/Recipe.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const recipeSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | }, 8 | ingredients: { 9 | type: [String], // Array of strings for ingredients 10 | required: true, 11 | }, 12 | instructions: { 13 | type: String, 14 | required: true, 15 | }, 16 | createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true } // Link to user who created the recipe 17 | }); 18 | 19 | const Recipe = mongoose.model('Recipe', recipeSchema); 20 | export default Recipe; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend-caproj", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "nodemon index.js", 8 | "start": "node index.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "description": "", 14 | "dependencies": { 15 | "body-parser": "^1.20.3", 16 | "cors": "^2.8.5", 17 | "dotenv": "^16.4.5", 18 | "express": "^4.21.1", 19 | "jsonwebtoken": "^9.0.2", 20 | "mongoose": "^8.7.2", 21 | "morgan": "^1.10.0", 22 | "pug": "^3.0.3" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^3.1.7" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /controllers/recipeController.js: -------------------------------------------------------------------------------- 1 | // importing the Recipe model to interact with the database 2 | import Recipe from "../models/Recipe"; 3 | 4 | // Controller to create a new recipe 5 | export const createRecipe = async (req,res) => { 6 | const{title, ingredients, instructions} = req.body; 7 | 8 | try{ 9 | // Create new recipe document using the Recipe model 10 | const newRecipe = new Recipe({title, ingredients, instructions}); 11 | // Saving new recipe to the database 12 | await newRecipe.save(); 13 | res.status(201).json(newRecipe); 14 | }catch (error){ 15 | // log an error 16 | console.log('Error creating recipe:', error) 17 | } 18 | }; -------------------------------------------------------------------------------- /controllers/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | 3 | const authMiddleware = (req, res, next) => { 4 | const token = req.header('Authorization')?.replace('Bearer ', ''); 5 | 6 | if (!token) { 7 | return res.status(401).json({ message: 'No token provided. Access denied.' }); 8 | } 9 | 10 | // Check if JWT_SECRET is loaded correctly 11 | if (!process.env.JWT_SECRET) { 12 | console.error("JWT_SECRET is not defined. Check your .env configuration."); 13 | return res.status(500).json({ message: 'Server configuration error.' }); 14 | } 15 | 16 | try { 17 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 18 | req.user = decoded; // This should contain { id: } 19 | next(); 20 | } catch (err) { 21 | console.error("Invalid token:", err); 22 | res.status(401).json({ message: 'Invalid token. Access denied.' }); 23 | } 24 | }; 25 | 26 | export default authMiddleware; 27 | -------------------------------------------------------------------------------- /controllers/userController.js: -------------------------------------------------------------------------------- 1 | import User from '../models/User.js'; 2 | import jwt from 'jsonwebtoken'; 3 | 4 | // Controller function to handle user login 5 | export const loginUser = async (req, res) => { 6 | const { username, password } = req.body; 7 | console.log("Login attempt:", username, password); // Debugging line 8 | 9 | try { 10 | const user = await User.findOne({ username }); 11 | if (!user || user.password !== password) { 12 | return res.status(400).json({ message: 'Invalid username or password' }); 13 | } 14 | 15 | // Ensure JWT_SECRET is set 16 | if (!process.env.JWT_SECRET) { 17 | console.error("JWT_SECRET not defined"); 18 | return res.status(500).json({ message: "Server configuration error" }); 19 | } 20 | 21 | const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30min' }); 22 | 23 | 24 | res.json({ 25 | token, 26 | username: user.username, 27 | }); 28 | } catch (error) { 29 | console.error("Server error:", error); 30 | res.status(500).json({ message: 'Server error' }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import mongoose from "mongoose"; 3 | import dotenv from "dotenv"; 4 | import morgan from "morgan"; 5 | import cors from "cors"; 6 | import recipesRouter from './routes/recipes.js'; 7 | import usersRouter from './routes/users.js'; 8 | import User from "./models/User.js"; 9 | 10 | dotenv.config(); 11 | 12 | const app = express(); 13 | const PORT = process.env.PORT || 3000; 14 | 15 | // ----Connecting to DB--- 16 | async function connectDB(){ 17 | try {await mongoose.connect(process.env.MONGODB_URI); 18 | console.log('Connected to MongoDB'); 19 | }catch(error) { 20 | console.error(error); 21 | }} 22 | 23 | // call the connectDB function 24 | connectDB(); 25 | 26 | // ---Middlewares--- 27 | app.use(morgan('dev')); // it's a logger 28 | app.use(express.json());// parse data to the body 29 | app.use(express.urlencoded({extended:true})); 30 | // cors allows backend to talk to frontend in the same machine 31 | app.use(cors()); 32 | 33 | // ---Routes--- 34 | 35 | app.use('/api/recipes', recipesRouter); 36 | app.use('/api/users', usersRouter); 37 | 38 | app.get('/', (req,res) =>{ 39 | res.send('Welcome to recipes world') 40 | }); 41 | app.post('/login', async(req,res)=>{ 42 | const{username, password} = req.body; 43 | 44 | // validation logic 45 | const user = await User.findOne({username}); //if user already exists in the database 46 | if (!user || user.password != password){ 47 | return res.status(400).strictContentLength({message: 'Invalid username or password'}); 48 | } 49 | 50 | { 51 | res.json({ 52 | token:'sample-token',//generating token 53 | email: req.email ||'simple_email@sample.com', 54 | username: req.username, 55 | picture: 'user_picture || default_picture_url' 56 | }); 57 | } 58 | }) 59 | 60 | 61 | // ---Error handling middlewares -- 62 | 63 | app.use((e,req,res,next) => { 64 | console.log(e); 65 | res.status(500).json({message: e.message, error:e}); 66 | }); 67 | 68 | app.listen(PORT, ()=> console.log(`Server running on port: ${PORT}`)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Capstone Backend Project 2 | 3 | # Description 4 | This is the Recipe Search backend application built with MERN(MongoDB, Express, React and Node.js). The app allows users to search for their vegetarian recipes and the details. The backend uses third party API, intereacts to fetch requested recipe data and manages user data using MongoDB. 5 | User Authentication:New user registration, login and authentication. 6 | Error Handling: Appropriate messages display or redirecting the users accordingly. 7 | CORS: allows interaction between frontend and backend when hosted. 8 | 9 | # Technologies 10 | * MongoDB: NoSQL database for storing data 11 | * Express: Web app framework for Node.js 12 | * Mongoose: ODM library for MongDB and Node.js 13 | * dotenv: Loads environmental variables from .env to process.env 14 | * Axios: HTTP client for making requests to third-party API. 15 | *Node.js: Backend javascript runtime environment.The file extensions are .js 16 | 17 | # how to install and run the project 18 | * Node.js: install Node.jsV12 or higher 19 | * MongoDB: set up MongoDB instance. 20 | * API Key: obtain API key from recipe data provider. 21 | 22 | # Folders 23 | * controllers - define core application logic (handling requests)for each resouce, like recipeController.js and userController.js. 24 | The authMiddleware.js file handles authentication in a Node.js/Express application, by checking the valid tokens. So, only authorized users are allowed.It can be imported only is route files where needed. For example, in the recipes.js. 25 | * models - Mongoose models define data schema for MongoDB collections. The recipeModel.js and userModel.js specifies validaton, data relatioships and structure. 26 | *routes - each route file, recipeRouter.js and userRouter.js, maps URL to controller methods and for different resources. 27 | * index.js - Main part of the server, connects with database, starts server and set up the express app. 28 | 29 | # usage of the project 30 | Users can search recipes, create own recipes and also safe to create login and authentication 31 | 32 | # include credits - if followed any tutorials, certain materails etc., 33 | followed Perscholas materials, youtube and stackflow. 34 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import User from "../models/User.js"; 3 | import {loginUser} from '../controllers/userController.js' 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * GET /api/users/ 9 | */ 10 | 11 | router.get('/', async(req,res) => { 12 | try { 13 | const users = await User.find(); 14 | res.json(users); 15 | } catch (error) { 16 | console.error("Error fetching users:", error); 17 | res.status(500).json({error: error.message}); 18 | } 19 | }); 20 | 21 | /** 22 | * GET /api/users/:id 23 | */ 24 | 25 | router.get("/:id", async(req,res) => { 26 | try{ 27 | const user = await User.findById(req.params.id); 28 | if(!user) return res.status(404).json({error: "User not found"}); 29 | }catch (error){ 30 | res.status(500).json({error: error.message}); 31 | } 32 | }); 33 | 34 | /** 35 | * POST /api/user @description create a new user 36 | */ 37 | router.post("/", async(req,res) =>{ 38 | try { 39 | const user = await User.create(req.body); 40 | res.status(201).json(user); 41 | } catch (error) { 42 | res.status(400).json({error: error.message}); 43 | } 44 | }); 45 | /** 46 | * POST /api/users/login 47 | */ 48 | router.post('/login', async (req,res) => { 49 | try { 50 | await loginUser(req,res); 51 | } catch (error) { 52 | console.error(error); 53 | res.status(500).json({message:'Server error'}); 54 | } 55 | }) 56 | 57 | /** 58 | * PUT /api/users/:id 59 | */ 60 | router.put("/:id", async (req,res) =>{ 61 | try { 62 | const updatedUser = await User.findByIdAndUpdate(req.params.id, req.body, {new: true,}); 63 | if(!updatedUser) 64 | {return res.status(404).json({error: "User not found"})} 65 | res.json(updatedUser); 66 | } catch (error) { 67 | res.status(500).json({error: error.message}); 68 | } 69 | }); 70 | 71 | /** 72 | * DELETE /api/user/:id 73 | */ 74 | 75 | router.delete("/:id", async(req,res) =>{ 76 | try { 77 | const user = await User.findByIdAndDelete(req.params.id); 78 | res.json({message: "User deleted"}); 79 | } catch (error) { 80 | res.status(500).json({error: error.message}); 81 | } 82 | }) 83 | 84 | export default router; -------------------------------------------------------------------------------- /routes/recipes.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import Recipe from "../models/Recipe.js"; 3 | import authMiddleware from '../controllers/authMiddleware.js'; 4 | 5 | const router = express.Router(); 6 | 7 | /** 8 | * GET /api/recipes/ 9 | * Fetches all recipes. 10 | */ 11 | router.get("/", async (req, res) => { 12 | try { 13 | const recipes = await Recipe.find(); 14 | res.json(recipes); 15 | } catch (err) { 16 | res.status(500).json({ error: err.message }); 17 | } 18 | }); 19 | 20 | /** 21 | * GET /api/recipes/my-recipes 22 | * Fetches recipes created by the logged-in user. 23 | * Protected route 24 | */ 25 | router.get("/my-recipes", authMiddleware, async (req, res) => { 26 | try { 27 | const userId = req.user.id; // Ensure req.user.id is accessible 28 | const recipes = await Recipe.find({ createdBy: userId }); 29 | res.json(recipes); 30 | } catch (error) { 31 | console.error("Error fetching user recipes:", error); 32 | res.status(500).json({ message: 'Error fetching user recipes' }); 33 | } 34 | }); 35 | 36 | /** 37 | * GET /api/recipes/:id 38 | * Fetches a single recipe by ID. 39 | */ 40 | router.get("/:id", async (req, res) => { 41 | try { 42 | const recipe = await Recipe.findById(req.params.id); 43 | if (!recipe) return res.status(404).json({ error: "Recipe not found" }); 44 | res.json(recipe); 45 | } catch (err) { 46 | res.status(500).json({ error: err.message }); 47 | } 48 | }); 49 | 50 | /** 51 | * POST /api/recipes 52 | * Creates a new recipe. 53 | * Protected route 54 | */ 55 | router.post("/", authMiddleware, async (req, res) => { 56 | const { title, ingredients, instructions } = req.body; 57 | const userId = req.user.id; 58 | 59 | if (!title || !Array.isArray(ingredients) || ingredients.length === 0 || !instructions) { 60 | return res.status(400).json({ error: 'Invalid data. Please provide title, ingredients (array), and instructions.' }); 61 | } 62 | 63 | try { 64 | const recipe = await Recipe.create({ 65 | title, 66 | ingredients, 67 | instructions, 68 | createdBy: userId 69 | }); 70 | res.status(201).json(recipe); 71 | } catch (err) { 72 | console.error("Error creating recipe:", err); 73 | res.status(500).json({ error: 'Internal server error. Please check server logs for more details.' }); 74 | } 75 | }); 76 | 77 | /** 78 | * PUT /api/recipes/:id 79 | * Updates a recipe by ID. 80 | */ 81 | router.put("/:id", async (req, res) => { 82 | try { 83 | const updatedRecipe = await Recipe.findByIdAndUpdate(req.params.id, req.body, { new: true }); 84 | if (updatedRecipe) { 85 | res.json(updatedRecipe); 86 | } else { 87 | res.status(404).json({ error: "Recipe not found" }); 88 | } 89 | } catch (err) { 90 | res.status(500).json({ error: err.message }); 91 | } 92 | }); 93 | 94 | /** 95 | * DELETE /api/recipes/:id 96 | * Deletes a recipe by ID. 97 | */ 98 | router.delete("/:id", async (req, res) => { 99 | try { 100 | const recipe = await Recipe.findByIdAndDelete(req.params.id); 101 | if (recipe) { 102 | res.json({ message: "Recipe deleted" }); 103 | } else { 104 | res.status(404).json({ error: "Recipe not found" }); 105 | } 106 | } catch (error) { 107 | res.status(500).json({ error: error.message }); 108 | } 109 | }); 110 | 111 | export default router; 112 | --------------------------------------------------------------------------------