En aquest tema, aprendrem a construir una eina de gestió de tasques utilitzant Node.js i Express. Aquest projecte ens permetrà aplicar molts dels conceptes apresos al llarg del curs, com ara la creació de servidors HTTP, la gestió de rutes, l'ús de bases de dades, l'autenticació i més.

Objectius del Projecte

  • Crear una API RESTful per gestionar tasques.
  • Implementar operacions CRUD (Crear, Llegir, Actualitzar, Eliminar).
  • Utilitzar MongoDB com a base de dades.
  • Implementar autenticació d'usuaris amb JWT.
  • Gestionar errors i validar dades.

Requisits Previs

  • Coneixements bàsics de Node.js i Express.
  • Coneixements bàsics de MongoDB.
  • Coneixements bàsics d'autenticació amb JWT.

Passos del Projecte

  1. Configuració del Projecte

1.1. Crear l'Estructura del Projecte

mkdir task-manager
cd task-manager
npm init -y
npm install express mongoose bcryptjs jsonwebtoken
npm install --save-dev nodemon

1.2. Configurar nodemon

Afegim un script al package.json per utilitzar nodemon durant el desenvolupament:

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

1.3. Crear l'Estructura de Carpetes

mkdir src
mkdir src/models
mkdir src/routes
mkdir src/controllers

  1. Configuració de l'Aplicació Express

2.1. Crear el Fitxer Principal index.js

const express = require('express');
const mongoose = require('mongoose');
const userRouter = require('./routes/user');
const taskRouter = require('./routes/task');

const app = express();
const port = process.env.PORT || 3000;

mongoose.connect('mongodb://localhost:27017/task-manager', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true,
});

app.use(express.json());
app.use(userRouter);
app.use(taskRouter);

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

  1. Models de Dades

3.1. Model d'Usuari (User)

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
    trim: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
    trim: true,
    lowercase: true,
  },
  password: {
    type: String,
    required: true,
    minlength: 7,
    trim: true,
  },
  tokens: [{
    token: {
      type: String,
      required: true,
    },
  }],
}, {
  timestamps: true,
});

userSchema.pre('save', async function (next) {
  const user = this;
  if (user.isModified('password')) {
    user.password = await bcrypt.hash(user.password, 8);
  }
  next();
});

userSchema.methods.generateAuthToken = async function () {
  const user = this;
  const token = jwt.sign({ _id: user._id.toString() }, 'secretkey');
  user.tokens = user.tokens.concat({ token });
  await user.save();
  return token;
};

const User = mongoose.model('User', userSchema);

module.exports = User;

3.2. Model de Tasca (Task)

const mongoose = require('mongoose');

const taskSchema = new mongoose.Schema({
  description: {
    type: String,
    required: true,
    trim: true,
  },
  completed: {
    type: Boolean,
    default: false,
  },
  owner: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'User',
  },
}, {
  timestamps: true,
});

const Task = mongoose.model('Task', taskSchema);

module.exports = Task;

  1. Rutes i Controladors

4.1. Rutes d'Usuari (user.js)

const express = require('express');
const User = require('../models/user');
const auth = require('../middleware/auth');
const router = new express.Router();

router.post('/users', async (req, res) => {
  const user = new User(req.body);
  try {
    await user.save();
    const token = await user.generateAuthToken();
    res.status(201).send({ user, token });
  } catch (e) {
    res.status(400).send(e);
  }
});

router.post('/users/login', async (req, res) => {
  try {
    const user = await User.findByCredentials(req.body.email, req.body.password);
    const token = await user.generateAuthToken();
    res.send({ user, token });
  } catch (e) {
    res.status(400).send();
  }
});

router.get('/users/me', auth, async (req, res) => {
  res.send(req.user);
});

module.exports = router;

4.2. Rutes de Tasca (task.js)

const express = require('express');
const Task = require('../models/task');
const auth = require('../middleware/auth');
const router = new express.Router();

router.post('/tasks', auth, async (req, res) => {
  const task = new Task({
    ...req.body,
    owner: req.user._id,
  });

  try {
    await task.save();
    res.status(201).send(task);
  } catch (e) {
    res.status(400).send(e);
  }
});

router.get('/tasks', auth, async (req, res) => {
  try {
    await req.user.populate('tasks').execPopulate();
    res.send(req.user.tasks);
  } catch (e) {
    res.status(500).send();
  }
});

router.get('/tasks/:id', auth, async (req, res) => {
  const _id = req.params.id;

  try {
    const task = await Task.findOne({ _id, owner: req.user._id });

    if (!task) {
      return res.status(404).send();
    }

    res.send(task);
  } catch (e) {
    res.status(500).send();
  }
});

router.patch('/tasks/:id', auth, async (req, res) => {
  const updates = Object.keys(req.body);
  const allowedUpdates = ['description', 'completed'];
  const isValidOperation = updates.every((update) => allowedUpdates.includes(update));

  if (!isValidOperation) {
    return res.status(400).send({ error: 'Invalid updates!' });
  }

  try {
    const task = await Task.findOne({ _id: req.params.id, owner: req.user._id });

    if (!task) {
      return res.status(404).send();
    }

    updates.forEach((update) => task[update] = req.body[update]);
    await task.save();
    res.send(task);
  } catch (e) {
    res.status(400).send(e);
  }
});

router.delete('/tasks/:id', auth, async (req, res) => {
  try {
    const task = await Task.findOneAndDelete({ _id: req.params.id, owner: req.user._id });

    if (!task) {
      return res.status(404).send();
    }

    res.send(task);
  } catch (e) {
    res.status(500).send();
  }
});

module.exports = router;

  1. Middleware d'Autenticació

5.1. Middleware auth.js

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

const auth = async (req, res, next) => {
  try {
    const token = req.header('Authorization').replace('Bearer ', '');
    const decoded = jwt.verify(token, 'secretkey');
    const user = await User.findOne({ _id: decoded._id, 'tokens.token': token });

    if (!user) {
      throw new Error();
    }

    req.token = token;
    req.user = user;
    next();
  } catch (e) {
    res.status(401).send({ error: 'Please authenticate.' });
  }
};

module.exports = auth;

  1. Proves i Depuració

6.1. Proves Unitàries

Utilitzarem mocha i chai per a les proves unitàries. Instal·lem les dependències:

npm install --save-dev mocha chai

6.2. Crear Fitxers de Prova

Creem una carpeta tests i afegim fitxers de prova per a usuaris i tasques.

  1. Desplegament

7.1. Configurar Variables d'Entorn

Utilitzem dotenv per gestionar les variables d'entorn:

npm install dotenv

Afegim el següent codi a index.js:

require('dotenv').config();

7.2. Desplegar a Heroku

Seguiu els passos per desplegar l'aplicació a Heroku, assegurant-vos de configurar les variables d'entorn necessàries.

Conclusió

En aquest projecte, hem construït una eina de gestió de tasques completa utilitzant Node.js i Express. Hem implementat operacions CRUD, autenticació amb JWT, i hem après a gestionar errors i validar dades. Aquest projecte ens ha permès aplicar molts dels conceptes apresos al llarg del curs i ens ha preparat per a desenvolupar aplicacions més complexes en el futur.

Curs de Node.js

Mòdul 1: Introducció a Node.js

Mòdul 2: Conceptes Bàsics

Mòdul 3: Sistema de Fitxers i I/O

Mòdul 4: HTTP i Servidors Web

Mòdul 5: NPM i Gestió de Paquets

Mòdul 6: Framework Express.js

Mòdul 7: Bases de Dades i ORMs

Mòdul 8: Autenticació i Autorització

Mòdul 9: Proves i Depuració

Mòdul 10: Temes Avançats

Mòdul 11: Desplegament i DevOps

Mòdul 12: Projectes del Món Real

© Copyright 2024. Tots els drets reservats