Dernière activité 1755883036

gpt-oss:20b.md Brut

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

{
  "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

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

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

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

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

const jwt = require('jsonwebtoken');
const User = require('../models/User');

const protect = async (req, res, next) => {
  let token;
  // Expected format: Authorization: Bearer <token>
  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

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

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

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

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

curl -X POST http://localhost:5000/api/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"secret123"}'

Response contains a JWT (token) and the user object.

10.2 Log‑in

curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"secret123"}'

Save the token from the response.

10.3 Create an item

curl -X POST http://localhost:5000/api/items \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -d '{"title":"My first item","description":"Hello world!"}'

10.4 Get all items

curl -X GET http://localhost:5000/api/items \
  -H "Authorization: Bearer <YOUR_TOKEN>"

10.5 Get a single item

curl -X GET http://localhost:5000/api/items/<ITEM_ID> \
  -H "Authorization: Bearer <YOUR_TOKEN>"

10.6 Update an item

curl -X PUT http://localhost:5000/api/items/<ITEM_ID> \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <YOUR_TOKEN>" \
  -d '{"title":"Updated title"}'

10.7 Delete an item

curl -X DELETE http://localhost:5000/api/items/<ITEM_ID> \
  -H "Authorization: Bearer <YOUR_TOKEN>"

⚙️ 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!