Es ist erfreulich, dass es jetzt endlich einen würdigen Anwärter auf den Platz des Hauptweb-Frameworks für alle und alles gibt - ich meine nicht Fastify.js, sondern natürlich Nest.js. Obwohl in Bezug auf quantitative Indikatoren der Popularität, ist es sehr, sehr weit von Express.js.
Tabelle. Kennzahlen zur Beliebtheit von Paketen von npmjs.org, github.com
| Nein. | Paket | Anzahl der Downloads | Anzahl der "Sterne" |
|---|---|---|---|
| einer | verbinden | 4 373 963 | 9100 |
| 2 | ausdrücken | 16 492 569 | 52.900 |
| 3 | Koa | 844 877 | 31.100 |
| vier | nestjs | 624 603 | 36.700 |
| fünf | Hapi | 389 530 | 13.200 |
| 6 | beschleunigen | 216 240 | 18.600 |
| 7 | restifizieren | 93.665 | 10 100 |
| acht | Polka | 71 394 | 4.700 |
Express.js funktioniert immer noch in über 2/3 der Webanwendungen von node.js. Darüber hinaus verwenden 2/3 der beliebtesten Webframeworks für node.js Express.js-Ansätze. (Es wäre genauer zu sagen, die Ansätze der Connect.js-Bibliothek, auf der Express.js vor Version 4 basierte).
In diesem Beitrag werden die Funktionen der wichtigsten Webframeworks für node.js erläutert und erläutert, was Fastify.js zu einer anderen Framework-Ebene macht, sodass Sie es als Framework für die Entwicklung Ihres nächsten Projekts auswählen können.
Kritik an Frameworks basierend auf synchroner Middleware
Was könnte an dieser Art von Code falsch sein?
app.get('/', (req, res) => {
res.send('Hello World!')
})
1. Die Funktion, die die Route verarbeitet, gibt keinen Wert zurück. Stattdessen müssen Sie eine der Methoden für das Antwortobjekt (res) aufrufen. Wenn diese Methode auch nach der Rückkehr der Funktion nicht explizit aufgerufen wird, warten Client und Server bis zum Ablauf jedes Timeouts auf die Antwort des Servers. Dies sind nur „direkte Verluste“, aber es gibt auch „entgangene Gewinne“. Die Tatsache, dass diese Funktion keinen Wert zurückgibt, macht es unmöglich, die angeforderte Funktionalität einfach zu implementieren, z. B. die Validierung oder Protokollierung von an den Client zurückgegebenen Antworten.
2. In Express.js ist die integrierte Fehlerbehandlung immer synchron. Es kommt jedoch selten vor, dass eine Route auf Aufrufe von asynchronen Vorgängen verzichtet. Da Express.js im vorindustriellen Zeitalter erstellt wurde, funktioniert der standardmäßige synchrone Fehlerhandler für asynchrone Fehler nicht, und asynchrone Fehler sollten wie folgt behandelt werden:
app.get('/', async (req, res, next) => {
try {
...
} catch (ex) {
next(ex);
}
})
oder so:
app.get('/', (req, res, next) => {
doAsync().catch(next)
})
3. Komplexität der asynchronen Initialisierung von Diensten. Beispielsweise arbeitet eine Anwendung mit einer Datenbank und greift als Dienst auf die Datenbank zu, indem eine Referenz in einer Variablen gespeichert wird. Die Routeninitialisierung von Express.js ist immer synchron. Dies bedeutet, dass die asynchrone Initialisierung des Dienstes höchstwahrscheinlich noch keine Zeit hat, um zu arbeiten, wenn die ersten Clientanforderungen auf den Routen eintreffen. Daher müssen Sie den asynchronen Code in die Routen "ziehen", um ihn zu erhalten ein Link zu diesem Dienst. All dies ist natürlich realisierbar. Aber es geht zu weit von der naiven Einfachheit des ursprünglichen Codes:
app.get('/', (req, res) => {
res.send('Hello World!')
})
4. Und zu guter Letzt. Die meisten Express.js-Anwendungen führen ungefähr Folgendes aus:
app.use(someFuction);
app.use(anotherFunction());
app.use((req, res, nexn) => ..., next());
app.get('/', (req, res) => {
res.send('Hello World!')
})
Wenn Sie Ihren Teil der Anwendung entwickeln, können Sie sicher sein, dass die 10-20-Middleware bereits vor Ihrem Code funktioniert hat, der alle möglichen Eigenschaften des req-Objekts aufhängt und sogar die ursprüngliche Anforderung genau wie tatsächlich ändern kann dass die gleiche Menge, wenn nicht mehr Middleware hinzugefügt werden kann, nachdem Sie Ihren Teil der Anwendung entwickelt haben. Obwohl in der Express.js-Dokumentation das res.locals-Objekt übrigens mehrdeutig empfohlen wird, um zusätzliche Eigenschaften anzuhängen:
// Express.js
app.use(function (req, res, next) {
res.locals.user = req.user
res.locals.authenticated = !req.user.anonymous
next()
})
Historische Versuche, die Mängel von Express.js zu überwinden
Es überrascht nicht, dass der Hauptautor von Express.js und Connect.js - TJ Holowaychuk - das Projekt verlassen hat, um mit der Entwicklung des neuen Koa.js-Frameworks zu beginnen. Koa.js fügt Express.js Asynchronität hinzu. Mit diesem Code müssen beispielsweise keine asynchronen Fehler im Code jeder Route abgefangen werden, und der Handler wird in eine Middleware eingefügt:
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
};
}
})
Die frühesten Versionen von Koa.js hatten die Absicht, Generatoren für asynchrone Aufrufe einzuführen:
// 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;
});
Die Einführung von async / await hat die Nützlichkeit dieses Teils von Koa.js zunichte gemacht, und jetzt gibt es selbst in der Framework-Dokumentation keine derartigen Beispiele.
Fast das gleiche Alter wie Express.js - das Hapi.js-Framework. Controller in Hapi.js geben bereits einen Wert zurück, der gegenüber Express.js verbessert wurde. Keine vergleichbare Popularität wie Express.js, eine Komponente des Hapi.js-Projekts - die Joi-Bibliothek, die 3.388.762 Downloads von npmjs.org enthält und jetzt sowohl im Backend als auch im Frontend verwendet wird, ist mega-erfolgreich geworden. Die Erkenntnis, dass die Validierung eingehender Objekte kein Sonderfall ist, sondern ein notwendiges Attribut jeder Anwendung - die Validierung in Hapi.js wurde als Teil des Frameworks und als Parameter in die Definition der Route aufgenommen:
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)
})
}
}
});
Derzeit ist die Joi-Bibliothek ein eigenständiges Projekt.
Wenn wir ein Objektvalidierungsschema definiert haben, haben wir das Objekt selbst definiert. Es bleibt nur noch sehr wenig Zeit, um eine selbstdokumentierende Route zu erstellen, bei der eine Änderung des Datenüberprüfungsschemas die Dokumentation ändert, sodass die Dokumentation immer mit dem Code übereinstimmt.
Eine der mit Abstand besten Lösungen in der API-Dokumentation ist swagger / openAPI. Es wäre sehr praktisch, wenn das Schema, Beschreibungen unter Berücksichtigung der Anforderungen von swagger / openAPI, sowohl zur Validierung als auch zur Erstellung von Dokumentation verwendet werden könnte.
Fastify.js
Lassen Sie mich die Anforderungen zusammenfassen, die mir bei der Auswahl eines Webframeworks als wesentlich erscheinen:
- ( ).
- .
- .
- / .
- .
- .
Alle diese Punkte entsprechen Nest.js, mit dem ich derzeit an mehreren Projekten arbeite. Eine Funktion von Nest.js ist die weit verbreitete Verwendung von Dekoratoren, die in einigen Fällen eine Einschränkung darstellen kann, wenn die technischen Anforderungen die Verwendung von Standard-JavaScript vorschreiben (und wie Sie wissen, hat diese Situation mit der Standardisierung von Dekoratoren in JavaScript einige zum Stillstand gebracht vor Jahren, und es scheint, dass es nicht bald seine Lösung finden wird) ...
Daher kann eine Alternative das Fastify.js-Framework sein, dessen Funktionen ich jetzt analysieren werde.
Fastify.js unterstützt sowohl den Stil der Generierung einer Serverantwort, die den Entwicklern von Express.js vertraut ist, als auch vielversprechender in Form eines Funktionsrückgabewerts, während andere Antwortparameter (Status, Header) flexibel bearbeitet werden können:
// 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}
})
Die Fehlerbehandlung kann integriert (sofort einsatzbereit) und benutzerdefiniert sein.
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' };
});
}
Beide Optionen - synchron und asynchron - werden vom integrierten Fehlerbehandler auf dieselbe Weise behandelt. Natürlich gibt es immer nur wenige integrierte Funktionen. Passen wir den Fehlerbehandler an:
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' };
});
Dieser Teil des Codes ist vereinfacht (Fehler löst Literal aus). Ebenso können Sie einen benutzerdefinierten Fehler auslösen. (Das Definieren von benutzerdefinierten serialisierbaren Fehlern ist ein separates Thema, daher wird kein Beispiel angegeben.)
Zur Validierung verwendet Fastify.js die Bibliothek Ajv.js, die die Schnittstelle swagger / openAPI implementiert. Diese Tatsache ermöglicht es, Fastify.js in swagger / openAPI zu integrieren und die API selbst zu dokumentieren.
Standardmäßig ist die Validierung nicht die strengste (Felder sind optional und Felder, die nicht im Schema enthalten sind, sind zulässig). Um die Validierung streng zu gestalten, müssen die Parameter in der Ajv-Konfiguration und im Validierungsschema definiert werden:
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' };
});
}
Da das Schema der eingehenden Objekte bereits definiert wurde, muss beim Generieren der Swagger / OpenAPI-Dokumentation das Plugin installiert werden:
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,
});
Eine Antwortvalidierung ist ebenfalls möglich. Dazu müssen Sie das Plugin installieren:
fastify.register(require('fastify-response-validation'));
Die Validierung ist flexibel genug. Beispielsweise wird die Antwort jedes Status gemäß seinem eigenen Validierungsschema überprüft.
Den Code zum Schreiben des Artikels finden Sie hier .
Zusätzliche Informationsquellen
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 Jahre