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
