Below is a **minimal yet complete** CRUD REST API that: 1. **Creates / reads / updates / deletes** “items” in a MongoDB collection. 2. Uses **Express** as the web‑framework. 3. Stores data with **Mongoose** (MongoDB). 4. Secures the API with **JSON Web Tokens (JWT)** – users must sign‑up / log‑in, receive a token and include it in the `Authorization` header for all protected routes. Feel free to copy‑paste the files into a folder, run `npm install` and start the server. --- ## 1️⃣ Directory layout ``` crud-jwt-api/ ├─ .env ├─ package.json ├─ server.js ├─ config/ │ └─ db.js ├─ models/ │ ├─ User.js │ └─ Item.js ├─ middleware/ │ └─ auth.js ├─ routes/ │ ├─ auth.js │ └─ items.js └─ controllers/ ├─ authController.js └─ itemController.js ``` --- ## 2️⃣ `package.json` ```json { "name": "crud-jwt-api", "version": "1.0.0", "description": "CRUD REST API with Express, MongoDB & JWT auth", "main": "server.js", "scripts": { "start": "node server.js", "dev": "nodemon server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "bcrypt": "^5.1.0", "dotenv": "^16.3.1", "express": "^4.18.2", "jsonwebtoken": "^9.0.0", "mongoose": "^7.3.4" }, "devDependencies": { "nodemon": "^3.0.1" } } ``` > Run `npm install` first. --- ## 3️⃣ `.env` Create a `.env` file **at the project root**. Replace the placeholder values with your own. ``` PORT=5000 MONGO_URI=mongodb://localhost:27017/crud_jwt_api JWT_SECRET=supersecretkey123 # <-- keep this safe! JWT_EXPIRES_IN=1h ``` > For a production environment you should store these in a secrets manager. --- ## 4️⃣ `server.js` ```js require('dotenv').config(); const express = require('express'); const connectDB = require('./config/db'); const app = express(); // Connect to MongoDB connectDB(); // Middleware: parse JSON bodies app.use(express.json()); // Routes app.use('/api/auth', require('./routes/auth')); app.use('/api/items', require('./routes/items')); // Global error handler (optional but handy) app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ msg: 'Server Error' }); }); const PORT = process.env.PORT || 5000; app.listen(PORT, () => console.log(`Server listening on port ${PORT}`)); ``` --- ## 5️⃣ Database connection – `config/db.js` ```js const mongoose = require('mongoose'); const connectDB = async () => { try { await mongoose.connect(process.env.MONGO_URI, { // Options are optional in Mongoose 6+ }); console.log('MongoDB connected'); } catch (err) { console.error(err.message); process.exit(1); // exit on DB connect failure } }; module.exports = connectDB; ``` --- ## 6️⃣ Mongoose models ### 6.1 User – `models/User.js` ```js const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const UserSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true, lowercase: true, trim: true, }, password: { type: String, required: true, minlength: 6 }, createdAt: { type: Date, default: Date.now }, }); // Pre‑save hook to hash password UserSchema.pre('save', async function (next) { if (!this.isModified('password')) return next(); const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); next(); }); // Method to compare password UserSchema.methods.matchPassword = async function (enteredPassword) { return await bcrypt.compare(enteredPassword, this.password); }; module.exports = mongoose.model('User', UserSchema); ``` ### 6.2 Item – `models/Item.js` ```js const mongoose = require('mongoose'); const ItemSchema = new mongoose.Schema({ title: { type: String, required: true }, description: { type: String }, // Optional: link to owner owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, createdAt: { type: Date, default: Date.now }, }); module.exports = mongoose.model('Item', ItemSchema); ``` --- ## 7️⃣ JWT auth middleware – `middleware/auth.js` ```js const jwt = require('jsonwebtoken'); const User = require('../models/User'); const protect = async (req, res, next) => { let token; // Expected format: Authorization: Bearer if ( req.headers.authorization && req.headers.authorization.startsWith('Bearer') ) { token = req.headers.authorization.split(' ')[1]; } if (!token) { return res.status(401).json({ msg: 'Not authorized, token missing' }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); // Attach user to request (you could also only send _id) req.user = await User.findById(decoded.id).select('-password'); next(); } catch (err) { console.error(err); res.status(401).json({ msg: 'Not authorized, token invalid' }); } }; module.exports = { protect }; ``` --- ## 8️⃣ Auth routes & controller ### 8.1 `routes/auth.js` ```js const express = require('express'); const router = express.Router(); const { signup, login } = require('../controllers/authController'); // POST /api/auth/signup router.post('/signup', signup); // POST /api/auth/login router.post('/login', login); module.exports = router; ``` ### 8.2 `controllers/authController.js` ```js const User = require('../models/User'); const jwt = require('jsonwebtoken'); // Helper to create JWT const generateToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '1h', }); }; exports.signup = async (req, res) => { const { email, password } = req.body; if (!email || !password) return res.status(400).json({ msg: 'Please provide email & password' }); try { // Check if user exists let user = await User.findOne({ email }); if (user) return res.status(400).json({ msg: 'User already exists' }); user = new User({ email, password }); await user.save(); const token = generateToken(user._id); res.status(201).json({ token, user: { id: user._id, email: user.email }, }); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; exports.login = async (req, res) => { const { email, password } = req.body; if (!email || !password) return res.status(400).json({ msg: 'Please provide email & password' }); try { const user = await User.findOne({ email }); if (!user) return res.status(400).json({ msg: 'Invalid credentials' }); const isMatch = await user.matchPassword(password); if (!isMatch) return res.status(400).json({ msg: 'Invalid credentials' }); const token = generateToken(user._id); res.json({ token, user: { id: user._id, email: user.email }, }); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; ``` --- ## 9️⃣ Items routes & controller ### 9.1 `routes/items.js` ```js const express = require('express'); const router = express.Router(); const { getItems, getItem, createItem, updateItem, deleteItem, } = require('../controllers/itemController'); const { protect } = require('../middleware/auth'); // All routes below require a valid JWT router.use(protect); // GET /api/items router.get('/', getItems); // GET /api/items/:id router.get('/:id', getItem); // POST /api/items router.post('/', createItem); // PUT /api/items/:id router.put('/:id', updateItem); // DELETE /api/items/:id router.delete('/:id', deleteItem); module.exports = router; ``` ### 9.2 `controllers/itemController.js` ```js const Item = require('../models/Item'); // GET all items (you could paginate) exports.getItems = async (req, res) => { try { const items = await Item.find().sort({ createdAt: -1 }); res.json(items); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; // GET single item exports.getItem = async (req, res) => { try { const item = await Item.findById(req.params.id); if (!item) return res.status(404).json({ msg: 'Item not found' }); res.json(item); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; // CREATE new item exports.createItem = async (req, res) => { const { title, description } = req.body; if (!title) return res.status(400).json({ msg: 'Title is required' }); try { const newItem = new Item({ title, description, owner: req.user._id, // optional }); await newItem.save(); res.status(201).json(newItem); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; // UPDATE existing item exports.updateItem = async (req, res) => { const { title, description } = req.body; try { let item = await Item.findById(req.params.id); if (!item) return res.status(404).json({ msg: 'Item not found' }); // optional: restrict update to owner only // if (!item.owner.equals(req.user._id)) // return res.status(403).json({ msg: 'Not allowed' }); item.title = title ?? item.title; item.description = description ?? item.description; await item.save(); res.json(item); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; // DELETE item exports.deleteItem = async (req, res) => { try { const item = await Item.findById(req.params.id); if (!item) return res.status(404).json({ msg: 'Item not found' }); // optional: restrict delete to owner only // if (!item.owner.equals(req.user._id)) // return res.status(403).json({ msg: 'Not allowed' }); await item.remove(); res.json({ msg: 'Item removed' }); } catch (err) { console.error(err); res.status(500).json({ msg: 'Server error' }); } }; ``` --- ## 🔑 10️⃣ Using the API Below are example `curl` calls. Replace `localhost:5000` with your host if different. ### 10.1 Sign‑up ```bash curl -X POST http://localhost:5000/api/auth/signup \ -H "Content-Type: application/json" \ -d '{"email":"alice@example.com","password":"secret123"}' ``` Response contains a JWT (`token`) and the user object. ### 10.2 Log‑in ```bash curl -X POST http://localhost:5000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"alice@example.com","password":"secret123"}' ``` > Save the `token` from the response. ### 10.3 Create an item ```bash curl -X POST http://localhost:5000/api/items \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"title":"My first item","description":"Hello world!"}' ``` ### 10.4 Get all items ```bash curl -X GET http://localhost:5000/api/items \ -H "Authorization: Bearer " ``` ### 10.5 Get a single item ```bash curl -X GET http://localhost:5000/api/items/ \ -H "Authorization: Bearer " ``` ### 10.6 Update an item ```bash curl -X PUT http://localhost:5000/api/items/ \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"title":"Updated title"}' ``` ### 10.7 Delete an item ```bash curl -X DELETE http://localhost:5000/api/items/ \ -H "Authorization: Bearer " ``` --- ## ⚙️ 11️⃣ Things to improve for production | Area | Suggestion | |------|------------| | **Password policy** | Enforce stronger passwords, use OWASP guidelines. | | **Rate limiting / brute‑force protection** | Add `express-rate-limit`. | | **Refresh tokens** | Store a long‑lived refresh token in a cookie or DB. | | **HTTPS** | Use TLS in front of the API. | | **CORS** | Configure properly (`cors` package). | | **Input validation** | Use `express-validator` or `Joi`. | | **Logging** | Use Winston / Morgan with log rotation. | | **Testing** | Add unit / integration tests (`jest` / `supertest`). | | **Docker** | Containerise the app & MongoDB for reproducibility. | --- ### 🎉 You’re done! This example covers the core concepts: - **User signup & login** → JWT issuance - **Middleware** → protects routes - **CRUD** on a resource - **Mongoose** data modelling - **Error handling** basics Feel free to extend it—add pagination, sorting, file uploads, or any other feature you need. Happy coding!