Il est gratifiant que maintenant, enfin, il y ait un candidat digne pour la place du framework Web principal pour tout le monde et tout - je ne veux pas dire Fastify.js, mais, bien sûr, Nest.js. Bien qu'en termes d'indicateurs quantitatifs de popularité, il soit très, très loin d'Express.js.
Tableau. Statistiques de popularité des packages de npmjs.org, github.com
| Non. | Paquet | Nombre de téléchargements | Nombre "d'étoiles" |
|---|---|---|---|
| une | relier | 4 373 963 | 9100 |
| 2 | Express | 16 492 569 | 52 900 |
| 3 | koa | 844 877 | 31 100 |
| quatre | nestjs | 624 603 | 36 700 |
| cinq | hapi | 389 530 | 13 200 |
| 6 | attacher | 216 240 | 18 600 |
| 7 | restifier | 93 665 | 10 100 |
| huit | polka | 71394 | 4 700 |
Express.js fonctionne toujours dans plus des 2/3 des applications Web node.js. De plus, 2/3 des frameworks Web les plus populaires pour node.js utilisent des approches Express.js. (Il serait plus exact de dire, les approches de la bibliothèque Connect.js, sur laquelle Express.js était basé avant la version 4).
Cet article traite des fonctionnalités des principaux frameworks Web pour node.js et de ce qui fait de Fastify.js un niveau de framework différent, ce qui vous permet de le choisir comme cadre pour le développement de votre prochain projet.
Critique des frameworks basés sur un middleware synchrone
Quel pourrait être le problème avec ce type de code?
app.get('/', (req, res) => {
res.send('Hello World!')
})
1. La fonction qui traite l'itinéraire ne renvoie pas de valeur. Au lieu de cela, vous devez appeler l'une des méthodes de l'objet response (res). Si cette méthode n'est pas appelée explicitement, même après le retour de la fonction, le client et le serveur resteront dans un état d'attente de la réponse du serveur jusqu'à ce que chaque délai expire. Ce ne sont que des «pertes directes», mais il y a aussi des «pertes de profits». Le fait que cette fonction ne retourne pas de valeur rend impossible la simple implémentation de la fonctionnalité demandée, par exemple, la validation ou la journalisation des réponses renvoyées au client.
2. Dans Express.js, la gestion des erreurs intégrée est toujours synchrone. Cependant, il est rare qu'une route se passe d'appels à des opérations asynchrones. Étant donné qu'Express.js a été construit à l'ère préindustrielle, le gestionnaire d'erreurs synchrones standard pour les erreurs asynchrones ne fonctionnera pas, et les erreurs asynchrones doivent être gérées comme ceci:
app.get('/', async (req, res, next) => {
try {
...
} catch (ex) {
next(ex);
}
})
ou comme ça:
app.get('/', (req, res, next) => {
doAsync().catch(next)
})
3. Complexité de l'initialisation asynchrone des services. Par exemple, une application fonctionne avec une base de données et accède à la base de données en tant que service en stockant une référence dans une variable. L'initialisation de l'itinéraire Express.js est toujours synchrone. Cela signifie que lorsque les premières demandes client commencent à arriver sur les routes, l'initialisation asynchrone du service n'aura probablement pas encore le temps de fonctionner, vous devrez donc "faire glisser" le code asynchrone dans les routes pour obtenir un lien vers ce service. Tout cela est, bien sûr, réalisable. Mais cela va trop loin de la simplicité naïve du code d'origine:
app.get('/', (req, res) => {
res.send('Hello World!')
})
4. Et enfin, last but not least. La plupart des applications Express.js exécutent quelque chose comme ceci:
app.use(someFuction);
app.use(anotherFunction());
app.use((req, res, nexn) => ..., next());
app.get('/', (req, res) => {
res.send('Hello World!')
})
Lorsque vous développez votre partie de l'application, vous pouvez être sûr que le middleware 10-20 a déjà fonctionné avant votre code, qui accrochent toutes sortes de propriétés sur l'objet req, et peuvent même modifier la requête d'origine, exactement comme dans le fait que le même montant sinon plus de middleware peut être ajouté après avoir développé votre partie de l'application. Bien que, d'ailleurs, dans la documentation Express.js, l'objet res.locals soit recommandé de manière ambiguë pour attacher des propriétés supplémentaires:
// Express.js
app.use(function (req, res, next) {
res.locals.user = req.user
res.locals.authenticated = !req.user.anonymous
next()
})
Tentatives historiques pour surmonter les lacunes d'Express.js
Sans surprise, l'auteur principal d'Express.js et de Connect.js - TJ Holowaychuk - a quitté le projet pour commencer à développer le nouveau framework Koa.js. Koa.js ajoute l'asynchronie à Express.js. Par exemple, ce code élimine le besoin de détecter les erreurs asynchrones dans le code de chaque route et place le gestionnaire dans un middleware:
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
// will only respond with JSON
ctx.status = err.statusCode || err.status || 500;
ctx.body = {
message: err.message
};
}
})
Les premières versions de Koa.js avaient l'intention d'introduire des générateurs pour gérer les appels asynchrones:
// from http://blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators/
var request = Q.denodeify(require('request'));
// Example of calling library code that returns a promise
function doHttpRequest(url) {
return request(url).then(function(resultParams) {
// Extract just the response object
return resultParams[];
});
}
app.use(function *() {
// Example with a return value
var response = yield doHttpRequest('http://example.com/');
this.body = "Response length is " + response.body.length;
});
L'introduction de async / await a annulé l'utilité de cette partie de Koa.js, et maintenant il n'y a pas d'exemples de ce type, même dans la documentation du framework.
Presque le même âge qu'Express.js - le framework Hapi.js. Les contrôleurs dans Hapi.js renvoient déjà une valeur, ce qui est un pas en avant par rapport à Express.js. Ne gagne pas en popularité comparable à Express.js, un composant du projet Hapi.js - la bibliothèque Joi, qui compte 3 388 762 téléchargements à partir de npmjs.org, et qui est maintenant utilisée à la fois sur le backend et sur le frontend, est devenue un très grand succès. Réaliser que la validation des objets entrants n'est pas un cas particulier, mais un attribut nécessaire de chaque application - la validation dans Hapi.js a été incluse dans le cadre du cadre et en tant que paramètre dans la définition de l'itinéraire:
server.route({
method: 'GET',
path: '/hello/{name}',
handler: function (request, h) {
return `Hello ${request.params.name}!`;
},
options: {
validate: {
params: Joi.object({
name: Joi.string().min(3).max(10)
})
}
}
});
Actuellement, la bibliothèque Joi est un projet autonome.
Si nous avons défini un schéma de validation d'objet, alors nous avons défini l'objet lui-même. Il reste très peu de choses pour créer une route auto-documentée dans laquelle une modification du schéma de validation des données modifie la documentation, de sorte que la documentation correspond toujours au code.
De loin, l'une des meilleures solutions de la documentation de l'API est swagger / openAPI. Il serait très pratique que le schéma, les descriptions prenant en compte les exigences de swagger / openAPI, puissent être utilisés à la fois pour la validation et pour la génération de documentation.
Fastify.js
Permettez-moi de résumer les exigences qui me semblent essentielles lors du choix d'un framework web:
- ( ).
- .
- .
- / .
- .
- .
Tous ces points correspondent à Nest.js, avec lequel je travaille actuellement sur plusieurs projets. Une caractéristique de Nest.js est l'utilisation généralisée des décorateurs, ce qui peut dans certains cas être une limitation si les exigences techniques spécifient l'utilisation de JavaScript standard (et comme vous le savez, avec la standardisation des décorateurs en JavaScript, cette situation a bloqué quelques il y a des années, et il semble qu'il ne trouvera pas bientôt sa résolution) ...
Par conséquent, une alternative peut être le framework Fastify.js, dont je vais maintenant analyser les fonctionnalités.
Fastify.js prend en charge à la fois le style de génération d'une réponse serveur familier aux développeurs Express.js et plus prometteur sous la forme d'une valeur de retour de fonction, tout en laissant la possibilité de manipuler de manière flexible d'autres paramètres de réponse (statut, en-têtes):
// Require the framework and instantiate it
const fastify = require('fastify')({
logger: true
})
// Declare a route
fastify.get('/', (request, reply) => {
reply.send({ hello: 'world' })
})
// Run the server!
fastify.listen(3000, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
const fastify = require('fastify')({
logger: true
})
fastify.get('/', (request, reply) => {
reply.type('application/json').code(200)
return { hello: 'world' }
})
fastify.listen(3000, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
La gestion des erreurs peut être intégrée (prête à l'emploi) et personnalisée.
const createError = require('fastify-error');
const CustomError = createError('403_ERROR', 'Message: ', 403);
function raiseAsyncError() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new CustomError('Async Error')), 5000);
});
}
async function routes(fastify) {
fastify.get('/sync-error', async () => {
if (true) {
throw new CustomError('Sync Error');
}
return { hello: 'world' };
});
fastify.get('/async-error', async () => {
await raiseAsyncError();
return { hello: 'world' };
});
}
Les deux options - synchrone et asynchrone - sont gérées de la même manière par le gestionnaire d'erreurs intégré. Bien sûr, il y a toujours peu de capacités intégrées. Personnalisons le gestionnaire d'erreurs:
fastify.setErrorHandler((error, request, reply) => {
console.log(error);
reply.status(error.status || 500).send(error);
});
fastify.get('/custom-error', () => {
if (true) {
throw { status: 419, data: { a: 1, b: 2} };
}
return { hello: 'world' };
});
Cette partie du code est simplifiée (l'erreur lève littéralement). De même, vous pouvez lancer une erreur personnalisée. (La définition d'erreurs sérialisables personnalisées est une rubrique distincte, aucun exemple n'est donc fourni).
Pour la validation, Fastify.js utilise la bibliothèque Ajv.js, qui implémente l'interface swagger / openAPI. Ce fait permet d'intégrer Fastify.js avec swagger / openAPI et d'auto-documenter l'API.
Par défaut, la validation n'est pas la plus stricte (les champs sont facultatifs et les champs qui ne sont pas dans le schéma sont autorisés). Afin de rendre la validation stricte, il est nécessaire de définir les paramètres dans la configuration Ajv, et dans le schéma de validation:
const fastify = require('fastify')({
logger: true,
ajv: {
customOptions: {
removeAdditional: false,
useDefaults: true,
coerceTypes: true,
allErrors: true,
strictTypes: true,
nullable: true,
strictRequired: true,
},
plugins: [],
},
});
const opts = {
httpStatus: 201,
schema: {
description: 'post some data',
tags: ['test'],
summary: 'qwerty',
additionalProperties: false,
body: {
additionalProperties: false,
type: 'object',
required: ['someKey'],
properties: {
someKey: { type: 'string' },
someOtherKey: { type: 'number', minimum: 10 },
},
},
response: {
200: {
type: 'object',
additionalProperties: false,
required: ['hello'],
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' },
hello: { type: 'string' },
},
},
201: {
type: 'object',
additionalProperties: false,
required: ['hello-test'],
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' },
'hello-test': { type: 'string' },
},
},
},
},
};
fastify.post('/test', opts, async (req, res) => {
res.status(201);
return { hello: 'world' };
});
}
Le schéma des objets entrants étant déjà défini, générer la documentation swagger / openAPI revient à installer le plugin:
fastify.register(require('fastify-swagger'), {
routePrefix: '/api-doc',
swagger: {
info: {
title: 'Test swagger',
description: 'testing the fastify swagger api',
version: '0.1.0',
},
securityDefinitions: {
apiKey: {
type: 'apiKey',
name: 'apiKey',
in: 'header',
},
},
host: 'localhost:3000',
schemes: ['http'],
consumes: ['application/json'],
produces: ['application/json'],
},
hideUntagged: true,
exposeRoute: true,
});
La validation de la réponse est également possible. Pour ce faire, vous devez installer le plugin:
fastify.register(require('fastify-response-validation'));
La validation est suffisamment flexible. Par exemple, la réponse de chaque statut sera vérifiée selon son propre schéma de validation.
Le code lié à la rédaction de l'article peut être trouvé ici .
Sources d'informations supplémentaires
1. blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators
2. habr.com/ru/company/dataart/blog/312638
apapacy@gmail.com
Mai 4 2021 année