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
- 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:
1.3. Crear l'Estructura de Carpetes
- 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}`); });
- 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;
- 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;
- 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;
- Proves i Depuració
6.1. Proves Unitàries
Utilitzarem mocha
i chai
per a les proves unitàries. Instal·lem les dependències:
6.2. Crear Fitxers de Prova
Creem una carpeta tests
i afegim fitxers de prova per a usuaris i tasques.
- Desplegament
7.1. Configurar Variables d'Entorn
Utilitzem dotenv
per gestionar les variables d'entorn:
Afegim el següent codi a index.js
:
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
- Introducció a Express.js
- Configuració d'una Aplicació Express
- Middleware
- Routing en Express
- Gestió d'Errors
Mòdul 7: Bases de Dades i ORMs
- Introducció a les Bases de Dades
- Utilitzar MongoDB amb Mongoose
- Utilitzar Bases de Dades SQL amb Sequelize
- Operacions CRUD
Mòdul 8: Autenticació i Autorització
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb Mocha i Chai
- Proves d'Integració
- Depuració d'Aplicacions Node.js
Mòdul 10: Temes Avançats
Mòdul 11: Desplegament i DevOps
- Variables d'Entorn
- Utilitzar PM2 per a la Gestió de Processos
- Desplegar a Heroku
- Integració i Desplegament Continu