Última atividade 1757456555

gpt-oss:120b.md Bruto

Below is a complete, runnable example of a small CRUD REST API built with

Technology What it does
Node.js + Express HTTP server & routing
MongoDB + Mongoose Data persistence & schema definition
JWT (json‑webtoken) Stateless authentication
bcrypt Password hashing
dotenv Environment variables

The API manages a simple resource called Post (title + body) and provides user registration / login. All Post routes are protected – a valid JWT must be sent in the Authorization: Bearer <token> header.

⚠️ This is a learning‑oriented example.
In production you’d add more validation, rate‑limiting, HTTPS, proper error handling, refresh‑tokens, etc.


1. Project Structure

my-crud-api/
│
├─ .env                # environment variables (not committed)
├─ package.json
├─ README.md
│
├─ src/
│   ├─ index.js        # entry point (Express app)
│   ├─ config/
│   │   └─ db.js       # MongoDB connection
│   ├─ middleware/
│   │   └─ auth.js     # JWT verification middleware
│   ├─ models/
│   │   ├─ User.js
│   │   └─ Post.js
│   ├─ routes/
│   │   ├─ auth.js     # /api/auth (register, login)
│   │   └─ posts.js    # /api/posts (CRUD)
│   └─ utils/
│       └─ generateToken.js
│
└─ .gitignore

2. Install the dependencies

# create folder & cd into it
mkdir my-crud-api && cd my-crud-api

# initialise npm
npm init -y

# install runtime deps
npm i express mongoose dotenv jsonwebtoken bcryptjs

# install dev deps (optional but helpful)
npm i -D nodemon

Add the following scripts entry to package.json so you can run the server with npm run dev:

"scripts": {
  "start": "node src/index.js",
  "dev": "nodemon src/index.js"
}

3. Environment variables (.env)

Create a file named .env in the project root:

PORT=5000
MONGO_URI=mongodb://localhost:27017/crud-demo
JWT_SECRET=superSecretKeyChangeMe
JWT_EXPIRES_IN=1d          # 1 day, you can also use "7d", "12h", etc.

Never commit .env – add it to .gitignore.


4. Code

4.1 src/config/db.js – MongoDB connection

// src/config/db.js
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    const conn = await mongoose.connect(process.env.MONGO_URI, {
      // options are optional with Mongoose 6+
    });
    console.log(`MongoDB connected: ${conn.connection.host}`);
  } catch (err) {
    console.error('MongoDB connection error:', err);
    process.exit(1);
  }
};

module.exports = connectDB;

4.2 src/models/User.js – User schema

// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, 'Please add a name'],
    },
    email: {
      type: String,
      required: [true, 'Please add an email'],
      unique: true,
      lowercase: true,
      match: [
        /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/,
        'Please add a valid email',
      ],
    },
    password: {
      type: String,
      required: [true, 'Please add a password'],
      minlength: 6,
      select: false, // do NOT return password by default
    },
  },
  { timestamps: true }
);

// Hash password before saving
userSchema.pre('save', async function (next) {
  // only hash if the password field is modified (or new)
  if (!this.isModified('password')) {
    return next();
  }
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// Method to compare entered password with hashed password
userSchema.methods.matchPassword = async function (enteredPwd) {
  return await bcrypt.compare(enteredPwd, this.password);
};

module.exports = mongoose.model('User', userSchema);

4.3 src/models/Post.js – Post schema (the CRUD resource)

// src/models/Post.js
const mongoose = require('mongoose');

const postSchema = new mongoose.Schema(
  {
    title: {
      type: String,
      required: [true, 'Post title required'],
    },
    body: {
      type: String,
      required: [true, 'Post body required'],
    },
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User', // optional – you can link a post to its creator
      required: true,
    },
  },
  { timestamps: true }
);

module.exports = mongoose.model('Post', postSchema);

4.4 src/utils/generateToken.js – JWT helper

// src/utils/generateToken.js
const jwt = require('jsonwebtoken');

const generateToken = (payload) => {
  return jwt.sign(payload, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN || '1d',
  });
};

module.exports = generateToken;

4.5 src/middleware/auth.js – Protect routes

// src/middleware/auth.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');

const protect = async (req, res, next) => {
  let token;

  // Expect header: 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({ message: 'Not authorized, token missing' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    // Attach user (without password) to request
    req.user = await User.findById(decoded.id).select('-password');
    if (!req.user) {
      return res.status(401).json({ message: 'User not found' });
    }
    next();
  } catch (err) {
    console.error(err);
    return res.status(401).json({ message: 'Not authorized, token invalid' });
  }
};

module.exports = protect;

4.6 src/routes/auth.js – Register & Login

// src/routes/auth.js
const express = require('express');
const router = express.Router();
const User = require('../models/User');
const generateToken = require('../utils/generateToken');

// @route   POST /api/auth/register
// @desc    Register new user & return JWT
// @access  Public
router.post('/register', async (req, res) => {
  const { name, email, password } = req.body;

  // Simple validation
  if (!name || !email || !password) {
    return res.status(400).json({ message: 'Please provide all fields' });
  }

  try {
    // Check if user exists
    const existing = await User.findOne({ email });
    if (existing) {
      return res.status(400).json({ message: 'User already exists' });
    }

    const user = await User.create({ name, email, password });
    const token = generateToken({ id: user._id });

    res.status(201).json({
      token,
      user: { id: user._id, name: user.name, email: user.email },
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// @route   POST /api/auth/login
// @desc    Authenticate user & return JWT
// @access  Public
router.post('/login', async (req, res) => {
  const { email, password } = req.body;

  // Validation
  if (!email || !password) {
    return res.status(400).json({ message: 'Please provide email & password' });
  }

  try {
    // Include password field (select: false in schema)
    const user = await User.findOne({ email }).select('+password');
    if (!user) {
      return res.status(400).json({ message: 'Invalid credentials' });
    }

    const isMatch = await user.matchPassword(password);
    if (!isMatch) {
      return res.status(400).json({ message: 'Invalid credentials' });
    }

    const token = generateToken({ id: user._id });

    res.json({
      token,
      user: { id: user._id, name: user.name, email: user.email },
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

module.exports = router;

4.7 src/routes/posts.js – CRUD endpoints (protected)

// src/routes/posts.js
const express = require('express');
const router = express.Router();
const Post = require('../models/Post');
const protect = require('../middleware/auth');

// ---------- CREATE ----------
/**
 * @route   POST /api/posts
 * @desc    Create a new post (auth required)
 * @access  Private
 */
router.post('/', protect, async (req, res) => {
  const { title, body } = req.body;

  if (!title || !body) {
    return res.status(400).json({ message: 'Title and body are required' });
  }

  try {
    const post = await Post.create({
      title,
      body,
      author: req.user._id,
    });
    res.status(201).json(post);
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// ---------- READ ALL ----------
/**
 * @route   GET /api/posts
 * @desc    Get all posts (public or private, up to you)
 * @access  Public
 */
router.get('/', async (req, res) => {
  try {
    // Populate author name/email (optional)
    const posts = await Post.find()
      .populate('author', 'name email')
      .sort({ createdAt: -1 });
    res.json(posts);
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// ---------- READ ONE ----------
/**
 * @route   GET /api/posts/:id
 * @desc    Get a single post by ID
 * @access  Public
 */
router.get('/:id', async (req, res) => {
  try {
    const post = await Post.findById(req.params.id).populate(
      'author',
      'name email'
    );
    if (!post) {
      return res.status(404).json({ message: 'Post not found' });
    }
    res.json(post);
  } catch (err) {
    console.error(err);
    // CastError => invalid ObjectId format
    if (err.name === 'CastError')
      return res.status(400).json({ message: 'Invalid post ID' });
    res.status(500).json({ message: 'Server error' });
  }
});

// ---------- UPDATE ----------
/**
 * @route   PUT /api/posts/:id
 * @desc    Update a post (only author can edit)
 * @access  Private
 */
router.put('/:id', protect, async (req, res) => {
  const { title, body } = req.body;

  try {
    const post = await Post.findById(req.params.id);
    if (!post) return res.status(404).json({ message: 'Post not found' });

    // Only the author can modify
    if (post.author.toString() !== req.user._id.toString()) {
      return res
        .status(403)
        .json({ message: 'You are not authorized to edit this post' });
    }

    // Apply updates
    if (title) post.title = title;
    if (body) post.body = body;

    const updated = await post.save();
    res.json(updated);
  } catch (err) {
    console.error(err);
    if (err.name === 'CastError')
      return res.status(400).json({ message: 'Invalid post ID' });
    res.status(500).json({ message: 'Server error' });
  }
});

// ---------- DELETE ----------
/**
 * @route   DELETE /api/posts/:id
 * @desc    Delete a post (only author can delete)
 * @access  Private
 */
router.delete('/:id', protect, async (req, res) => {
  try {
    const post = await Post.findById(req.params.id);
    if (!post) return res.status(404).json({ message: 'Post not found' });

    // Only the author can delete
    if (post.author.toString() !== req.user._id.toString()) {
      return res
        .status(403)
        .json({ message: 'You are not authorized to delete this post' });
    }

    await post.remove();
    res.json({ message: 'Post removed' });
  } catch (err) {
    console.error(err);
    if (err.name === 'CastError')
      return res.status(400).json({ message: 'Invalid post ID' });
    res.status(500).json({ message: 'Server error' });
  }
});

module.exports = router;

4.8 src/index.js – Entry point

// src/index.js
require('dotenv').config(); // load .env first
const express = require('express');
const morgan = require('morgan');
const connectDB = require('./config/db');

const app = express();

// Connect to DB
connectDB();

// Middleware
app.use(express.json()); // parse JSON bodies
app.use(morgan('dev'));   // logging (optional)

// Routes
app.use('/api/auth', require('./routes/auth'));
app.use('/api/posts', require('./routes/posts'));

// 404 handler
app.use((req, res) => {
  res.status(404).json({ message: 'Endpoint not found' });
});

// Global error handler (optional but nice)
app.use((err, req, res, next) => {
  console.error('Unhandled error:', err);
  res.status(500).json({ message: 'Something went wrong' });
});

// Start server
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));

4.9 .gitignore

node_modules/
.env

4.10 README.md – How to run

# CRUD REST API with Express, MongoDB & JWT

A minimal example that shows:

* User registration & login (passwords hashed with bcrypt)
* JWT generation & verification
* Protected CRUD endpoints (`/api/posts`) that only the authenticated user can create / edit / delete

## Prerequisites

* **Node.js** (>= 14)
* **MongoDB** (local or Atlas). The example uses a local MongoDB instance at `mongodb://localhost:27017/crud-demo`.

## Setup

```bash
# Clone (or copy) the repo
git clone <repo-url>
cd my-crud-api

# Install deps
npm install

# Create a .env file (see .env.example)
cp .env.example .env
# Edit .env if needed

# Run the server (development)
npm run dev

Server will start on http://localhost:5000.

API Overview

Method Endpoint Description Auth
POST /api/auth/register Register a new user
POST /api/auth/login Login & receive JWT
POST /api/posts Create a post (title, body)
GET /api/posts List all posts
GET /api/posts/:id Get a single post
PUT /api/posts/:id Update post (author only)
DELETE /api/posts/:id Delete post (author only)

Authenticated requests must include the header:

Authorization: Bearer <jwt-token>

Example using curl

# 1️⃣ Register
curl -X POST http://localhost:5000/api/auth/register \
 -H "Content-Type: application/json" \
 -d '{"name":"Alice","email":"[email protected]","password":"secret123"}'

# 2️⃣ Login (returns token)
TOKEN=$(curl -s -X POST http://localhost:5000/api/auth/login \
 -H "Content-Type: application/json" \
 -d '{"email":"[email protected]","password":"secret123"}' | jq -r .token)

# 3️⃣ Create a post (protected)
curl -X POST http://localhost:5000/api/posts \
 -H "Content-Type: application/json" \
 -H "Authorization: Bearer $TOKEN" \
 -d '{"title":"My first post","body":"Hello world!"}'

Extending the Project

  • Add request validation with express-validator or Joi.
  • Use Refresh tokens for longer sessions.
  • Add pagination, filtering, and search on /api/posts.
  • Deploy to Heroku, Render, Vercel (serverless), etc.

Feel free to fork and build on top of this skeleton.


---

## Testing the Flow

1. **Register a user** → DB now contains a user with a hashed password.
2. **Login** → Server checks password, creates a JWT (`payload.id = userId`).
3. **Create a post** → Middleware `protect` reads `Authorization` header, verifies token, attaches `req.user`.
4. **Read posts** → No auth needed; returns list with author details populated.
5. **Update/Delete** → The middleware ensures the request is authenticated, then the route checks that `post.author` matches `req.user._id`.

If you send an invalid or expired token, the request will be rejected with a `401 Unauthorized` response.

---

That's the full, ready‑to‑run code for a CRUD API with JWT authentication using Express and MongoDB. Happy coding! 🎉

Quick Recap of How It Works

  1. User registers → password stored as a bcrypt hash.
  2. User logs in → bcrypt compares the supplied password with stored hash.
  3. Server creates a JWT (payload = { id: userId }) signed with a secret.
  4. Client stores the token (e.g., in localStorage, cookies, or in memory).
  5. For each protected route, the protect middleware extracts the token, verifies it (jwt.verify), and attaches the user document to req.user.
  6. CRUD logic can now safely rely on req.user to enforce ownership rules.

Enjoy building your own full‑stack apps on top of this foundation! 🚀


---

## Optional Enhancements (not required for the base assignment)

1. **Refresh Tokens** – store a long‑lived refresh token in DB and rotate.
2. **Rate Limiting** – protect `/api/auth` from brute‑force.
3. **Schema Validation** – use `express-validator` or `Joi` for request bodies.
4. **Testing** – write Jest/Supertest integration tests.

Feel free to ask if you'd like sample code for any of those.

This completes the full, functional CRUD API with authentication via JWT. Copy the files into a new folder, run npm install, create .env, and start the server. You now have a solid base to expand into a larger project!