Fastify.js ist nicht nur das schnellste Webframework für node.js

Express.js ist seit 10 Jahren das beliebteste Webframework für node.js. Jeder, der damit gearbeitet hat, weiß, dass komplexe Express.js-Anwendungen schwierig zu strukturieren sein können. Aber wie sie sagen, ist Gewohnheit eine zweite Natur. Es kann schwierig sein, Express.js aufzugeben. Zum Beispiel ist es schwierig, mit dem Rauchen aufzuhören. Es scheint, dass wir diese endlose Kette von Middleware unbedingt brauchen, und wenn wir ihnen die Möglichkeit nehmen, sie aus irgendeinem Grund und ohne Grund zu erstellen, wird das Projekt gestoppt.



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:



  1. ( ).
  2. .
  3. .
  4. / .
  5. .
  6. .


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



All Articles