- install pm2: $ sudo npm install pm2@latest -g
- execute : $ pm2 start 'npm start'
Les applications qui tournent sous PM2 seront redémarrées automatiquement si l’application crashe ou est arrêtée, mais nous pouvons prendre une mesure supplémentaire pour que l’application soit lancée au démarrage du système en utilisant la sous-commande startup. Cette sous-commande génère et configure un script de démarrage pour lancer PM2 et ses processus gérés aux démarrages du serveur :
$ pm2 startup systemd
"scripts": {
"start": "node app.js"
// "start": "NODE_ENV=production node app.js",
// "dev": "NODE_ENV=development nodemon app.js"
},
app.js
const express = require('express') const app = express() const port = 3000 app.get('/', (req, res) => res.send('Hello, Express!')) app.listen(port, () => console.log(`Notre application Node est démarrée sur : http://localhost:${port}`))
mock-pokemon.js
const pokemons = [ { id: 1, name: "Bulbizarre", hp: 25, cp: 5, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/001.png", types: ["Plante", "Poison"], created: new Date() }, { id: 2, name: "Salamèche", hp: 28, cp: 6, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/004.png", types: ["Feu"], created: new Date() }, { id: 3, name: "Carapuce", hp: 21, cp: 4, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/007.png", types: ["Eau"], created: new Date() }, { id: 4, name: "Aspicot", hp: 16, cp: 2, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/013.png", types: ["Insecte", "Poison"], created: new Date() }, { id: 5, name: "Roucool", hp: 30, cp: 7, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/016.png", types: ["Normal", "Vol"], created: new Date() }, { id: 6, name: "Rattata", hp: 18, cp: 6, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/019.png", types: ["Normal"], created: new Date() }, { id: 7, name: "Piafabec", hp: 14, cp: 5, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/021.png", types: ["Normal", "Vol"], created: new Date() }, { id: 8, name: "Abo", hp: 16, cp: 4, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/023.png", types: ["Poison"], created: new Date() }, { id: 9, name: "Pikachu", hp: 21, cp: 7, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/025.png", types: ["Electrik"], created: new Date() }, { id: 10, name: "Sabelette", hp: 19, cp: 3, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/027.png", types: ["Normal"], created: new Date() }, { id: 11, name: "Mélofée", hp: 25, cp: 5, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/035.png", types: ["Fée"], created: new Date() }, { id: 12, name: "Groupix", hp: 17, cp: 8, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/037.png", types: ["Feu"], created: new Date() } ]; module.exports = pokemons
helper.js
const success = (message, data) => { return { message: message, data: data } } exports.success
Les réponses JSON (Correction) : Retourner une liste de données au format JSON
const express = require('express') const { success } = require('./helper.js') let pokemons = require('./mock-pokemon') const app = express() const port = 3000 app.get('/', (req,res) => res.send('Hello again, Express !')) // On retourne la liste des pokémons au format JSON, avec un message : app.get('/api/pokemons', (req, res) => { const message = 'La liste des pokémons a bien été récupérée.' res.json(success(message, pokemons)) }) app.get('/api/pokemons/:id', (req, res) => { const id = parseInt(req.params.id) const pokemon = pokemons.find(pokemon => pokemon.id === id) const message = 'Un pokémon a bien été trouvé.' res.json(success(message, pokemon)) }) app.listen(port, () => console.log(`Notre application Node est démarrée sur : http://localhost:${port}`))
Une API Rest Complète : Ajouter un nouveau Pokémon
app.js
//... app.post('/api/pokemons', (req, res) => { const id = 123 const pokemonCreated = { ...req.body, ...{id: id, created: new Date()}} pokemons.push(pokemonCreated) const message = `Le pokémon ${pokemonCreated.name} a bien été crée.` res.json(success(message, pokemonCreated)) }) // ...
helper.js
exports.success = (message, data) => { return { message, data } } exports.getUniqueId = (pokemons) => { const pokemonsIds = pokemons.map(pokemon => pokemon.id) const maxId = pokemonsIds.reduce((a,b) => Math.max(a, b)) const uniqueId = maxId + 1 return uniqueId }
Une API Rest Complète : Effectuer une requête POST avec Insomnia
Le corps de la requête POST à copier-coller dans Insomnia pour ajouter le pokémon « Chenipan » sur votre API Rest.
{ "name": "Chenipan", "hp": 29, "cp":4, "picture": "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/010.png", "types": ["Insecte", "Poison"] }
Une API Rest Complète : Modifier un Pokémon
app.js
// ... app.put('/api/pokemons/:id', (req, res) => { const id = parseInt(req.params.id); const pokemonUpdated = { ...req.body, id: id } pokemons = pokemons.map(pokemon => { return pokemon.id === id ? pokemonUpdated : pokemon }) const message = `Le pokémon ${pokemonUpdated.name} a bien été modifié.` res.json(success(message, pokemonUpdated)) }); // ...
** Une API Rest Complète : Supprimer un Pokémon **
** app.js **
// ... app.delete('/api/pokemons/:id', (req, res) => { const id = parseInt(req.params.id) const pokemonDeleted = pokemons.find(pokemon => pokemon.id === id) pokemons = pokemons.filter(pokemon => pokemon.id !== id) const message = `Le pokémon ${pokemonDeleted.name} a bien été supprimé.` res.json(success(message, pokemonDeleted)) }); // ...
** L’API Rest et la Base de données : Créer un modèle Sequelize **
** src/models/pokemon.js **
module.exports = (sequelize, DataTypes) => { return sequelize.define('Pokemon', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING, allowNull: false }, hp: { type: DataTypes.INTEGER, allowNull: false }, cp: { type: DataTypes.INTEGER, allowNull: false }, picture: { type: DataTypes.STRING, allowNull: false }, types: { type: DataTypes.STRING, allowNull: false } }, { timestamps: true, createdAt: 'created', updatedAt: false }) }
** L’API Rest et la Base de données : Créer un modèle Sequelize **
** sequelize.js **
const { Sequelize, DataTypes } = require('sequelize') const PokemonModel = require('../models/pokemon') const pokemons = require('./mock-pokemon') const sequelize = new Sequelize('pokedex', 'root', '', { host: 'localhost', dialect: 'mariadb', dialectOptions: { timezone: 'Etc/GMT-2', }, logging: false }) const Pokemon = PokemonModel(sequelize, DataTypes) const initDb = () => { return sequelize.sync({force: true}).then(_ => { pokemons.map(pokemon => { Pokemon.create({ name: pokemon.name, hp: pokemon.hp, cp: pokemon.cp, picture: pokemon.picture, types: pokemon.types.join() }).then(pokemon => console.log(pokemon.toJSON())) }) console.log('La base de donnée a bien été initialisée !') }) } module.exports = { initDb, Pokemon }
** findAllPokemons.js **
const { Pokemon } = require('../db/sequelize') module.exports = (app) => { app.get('/api/pokemons', (req, res) => { Pokemon.findAll() .then(pokemons => { const message = 'La liste des pokémons a bien été récupérée.' res.json({ message, data: pokemons }) }) }) }
_findPokemonByPk.js_
const { Pokemon } = require('../db/sequelize') module.exports = (app) => { app.get('/api/pokemons/:id', (req, res) => { Pokemon.findByPk(req.params.id) .then(pokemon => { const message = 'Un pokémon a bien été trouvé.' res.json({ message, data: pokemon }) }) }) }
**_createPokemon.js_**
const { Pokemon } = require('../db/sequelize') module.exports = (app) => { app.post('/api/pokemons', (req, res) => { Pokemon.create(req.body) .then(pokemon => { const message = `Le pokémon ${req.body.name} a bien été crée.` res.json({ message, data: pokemon }) }) }) }
_updatePokemon.js_
const { Pokemon } = require('../db/sequelize') module.exports = (app) => { app.put('/api/pokemons/:id', (req, res) => { const id = req.params.id Pokemon.update(req.body, { where: { id: id } }) .then(_ => { Pokemon.findByPk(id).then(pokemon => { const message = `Le pokémon ${pokemon.name} a bien été modifié.` res.json({message, data: pokemon }) }) }) }) }
****_**deletePokemon___.js**
const { Pokemon } = require('../db/sequelize') module.exports = (app) => { app.delete('/api/pokemons/:id', (req, res) => { Pokemon.findByPk(req.params.id).then(pokemon => { const pokemonDeleted = pokemon; Pokemon.destroy({ where: { id: pokemon.id } }) .then(_ => { const message = `Le pokémon avec l'identifiant n°${pokemonDeleted.id} a bien été supprimé.` res.json({message, data: pokemonDeleted }) }) }) }) }
** Authentification : Créer un modèle User avec Sequelize **
** models/user.js **
module.exports = (sequelize, DataTypes) => { return sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, username: { type: DataTypes.STRING }, password: { type: DataTypes.STRING } }) }
** _ routes/login.js _ **
const { User } = require('../db/sequelize') const bcrypt = require('bcrypt') module.exports = (app) => { app.post('/api/login', (req, res) => { User.findOne({ where: { username: req.body.username } }).then(user => { bcrypt.compare(req.body.password, user.password).then(isPasswordValid => { if(isPasswordValid) { const message = `L'utilisateur a été connecté avec succès`; return res.json({ message, data: user }) } }) }) }) }
_ auth/auth.js _
const jwt = require('jsonwebtoken') const privateKey = require('../auth/private_key') module.exports = (req, res, next) => { const authorizationHeader = req.headers.authorization if(!authorizationHeader) { const message = `Vous n'avez pas fourni de jeton d'authentification. Ajoutez-en un dans l'en-tête de la requête.` return res.status(401).json({ message }) } const token = authorizationHeader.split(' ')[1] const decodedToken = jwt.verify(token, privateKey, (error, decodedToken) => { if(error) { const message = `L'utilisateur n'est pas autorisé à accèder à cette ressource.` return res.status(401).json({ message, data: error }) } const userId = decodedToken.userId if (req.body.userId && req.body.userId !== userId) { const message = `L'identifiant de l'utilisateur est invalide.` res.status(401).json({ message }) } else { next() } }) }