É gratificante que agora, finalmente, exista um concorrente digno para o lugar do framework web principal para todos e tudo - não me refiro a Fastify.js, mas, é claro, Nest.js. Embora em termos de indicadores quantitativos de popularidade, esteja muito, muito longe do Express.js.
Mesa. Métricas de popularidade de pacotes em npmjs.org, github.com
| Não. | Pacote | Número de downloads | Número de "estrelas" |
|---|---|---|---|
| 1 | conectar | 4 373 963 | 9100 |
| 2 | expressar | 16 492 569 | 52.900 |
| 3 | koa | 844 877 | 31.100 |
| quatro | Nestjs | 624 603 | 36.700 |
| cinco | Hapi | 389 530 | 13.200 |
| 6 | fastify | 216 240 | 18.600 |
| 7 | restificar | 93.665 | 10 100 |
| oito | polca | 71 394 | 4.700 |
Express.js ainda funciona em mais de 2/3 dos aplicativos da web node.js. Além disso, 2/3 das estruturas da Web mais populares para node.js usam abordagens Express.js. (Seria mais preciso dizer, as abordagens da biblioteca Connect.js, na qual Express.js era baseado antes da versão 4).
Esta postagem discute os recursos dos principais frameworks da web para node.js e o que torna Fastify.js um nível diferente de framework, o que permite escolhê-lo como um framework para desenvolver seu próximo projeto.
Críticas a frameworks baseados em middleware síncrono
O que pode haver de errado com esse tipo de código?
app.get('/', (req, res) => {
res.send('Hello World!')
})
1. A função que processa a rota não retorna um valor. Em vez disso, você deve chamar um dos métodos no objeto de resposta (res). Se esse método não for chamado explicitamente, mesmo após o retorno da função, o cliente e o servidor permanecerão em um estado de espera pela resposta do servidor até que cada tempo limite expire. Estas são apenas “perdas diretas”, mas também existem “lucros cessantes”. O fato dessa função não retornar um valor torna impossível simplesmente implementar a funcionalidade solicitada, por exemplo, validação ou registro das respostas retornadas ao cliente.
2. No Express.js, o tratamento de erros integrado é sempre síncrono. No entanto, é raro que uma rota fique sem chamadas para operações assíncronas. Como o Express.js foi criado na era pré-industrial, o manipulador de erros síncronos padrão para erros assíncronos não funcionará, e os erros assíncronos devem ser tratados assim:
app.get('/', async (req, res, next) => {
try {
...
} catch (ex) {
next(ex);
}
})
ou assim:
app.get('/', (req, res, next) => {
doAsync().catch(next)
})
3. Complexidade de inicialização assíncrona de serviços. Por exemplo, um aplicativo funciona com um banco de dados e acessa o banco de dados como um serviço, armazenando uma referência em uma variável. A inicialização da rota Express.js é sempre síncrona. Isso significa que quando as primeiras solicitações do cliente começarem a chegar nas rotas, a inicialização assíncrona do serviço, muito provavelmente, ainda não terá tempo de funcionar, então você terá que "arrastar" o código assíncrono nas rotas para obter um link para este serviço. Tudo isso é, claro, realizável. Mas vai muito longe da simplicidade ingênua do código original:
app.get('/', (req, res) => {
res.send('Hello World!')
})
4. E por último, mas não menos importante. A maioria dos aplicativos Express.js executa algo assim:
app.use(someFuction);
app.use(anotherFunction());
app.use((req, res, nexn) => ..., next());
app.get('/', (req, res) => {
res.send('Hello World!')
})
Ao desenvolver sua parte do aplicativo, você pode ter certeza de que o middleware 10-20 já funcionou antes do seu código, que pendura todos os tipos de propriedades no objeto req e até pode modificar a solicitação original, exatamente como no fato que a mesma quantidade, senão mais, middleware pode ser adicionado após desenvolver sua parte do aplicativo. Embora, a propósito, na documentação do Express.js, o objeto res.locals seja ambiguamente recomendado para anexar propriedades adicionais:
// Express.js
app.use(function (req, res, next) {
res.locals.user = req.user
res.locals.authenticated = !req.user.anonymous
next()
})
Tentativas históricas de superar as deficiências do Express.js
Não surpreendentemente, o autor principal de Express.js e Connect.js - TJ Holowaychuk - deixou o projeto para começar a desenvolver o novo framework Koa.js. Koa.js adiciona assincronia a Express.js. Por exemplo, este código elimina a necessidade de capturar erros assíncronos no código de cada rota e coloca o manipulador em um 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
};
}
})
As primeiras versões do Koa.js tinham a intenção de apresentar geradores para lidar com chamadas assíncronas:
// 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;
});
A introdução de async / await negou a utilidade desta parte do Koa.js, e agora não há tais exemplos nem mesmo na documentação do framework.
Quase da mesma idade que Express.js - a estrutura Hapi.js. Os controladores no Hapi.js já retornam um valor, que é um passo à frente do Express.js. Não ganhando popularidade comparável ao Express.js, um componente do projeto Hapi.js - a biblioteca Joi, que tem 3.388.762 downloads de npmjs.org e agora é usada tanto no backend quanto no frontend, tornou-se um mega-sucesso. Perceber que a validação de objetos recebidos não é um caso especial, mas um atributo necessário de cada aplicativo - a validação em Hapi.js foi incluída como parte do framework e como um parâmetro na definição da rota:
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)
})
}
}
});
Atualmente, a biblioteca Joi é um projeto autônomo.
Se definimos um esquema de validação de objeto, definimos o próprio objeto. Resta muito pouco para criar uma rota de autodocumentação na qual uma mudança no esquema de validação de dados altera a documentação, de forma que a documentação sempre corresponda ao código.
De longe, uma das melhores soluções na documentação da API é swagger / openAPI. Seria muito conveniente se o esquema, as descrições levando em consideração os requisitos de swagger / openAPI, pudessem ser usados para validação e para gerar documentação.
Fastify.js
Deixe-me resumir os requisitos que me parecem essenciais ao escolher uma estrutura da web:
- ( ).
- .
- .
- / .
- .
- .
Todos esses pontos correspondem ao Nest.js, com o qual estou atualmente trabalhando em vários projetos. Uma característica do Nest.js é o amplo uso de decoradores, o que pode em alguns casos ser uma limitação se os requisitos técnicos especificam o uso de JavaScript padrão (e como você sabe, com a padronização de decoradores em JavaScript, essa situação parou alguns anos atrás, e parece que não encontrará em breve a sua resolução) ...
Portanto, uma alternativa pode ser o framework Fastify.js, cujos recursos analisarei agora.
Fastify.js suporta o estilo de geração de uma resposta do servidor que é familiar aos desenvolvedores Express.js e mais promissor na forma de um valor de retorno de função, deixando a capacidade de manipular com flexibilidade outros parâmetros de resposta (status, cabeçalhos):
// 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}
})
O tratamento de erros pode ser integrado (pronto para uso) e personalizado.
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' };
});
}
Ambas as opções - síncrona e assíncrona - são tratadas da mesma maneira pelo manipulador de erros integrado. Obviamente, sempre há poucos recursos integrados. Vamos personalizar o gerenciador de erros:
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' };
});
Esta parte do código é simplificada (o erro gera literal). Da mesma forma, você pode lançar um erro personalizado. (Definir erros serializáveis personalizados é um tópico separado, portanto, nenhum exemplo é fornecido).
Para validação, Fastify.js usa a biblioteca Ajv.js, que implementa a interface swagger / openAPI. Este fato torna possível integrar Fastify.js com swagger / openAPI e autodocumentar a API.
Por padrão, a validação não é a mais estrita (os campos são opcionais e os campos que não estão no esquema são permitidos). Para tornar a validação estrita, é necessário definir os parâmetros na configuração do Ajv e no esquema de validação:
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' };
});
}
Como o esquema dos objetos de entrada já foi definido, a geração da documentação swagger / openAPI se resume à instalação do plug-in:
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,
});
A validação de resposta também é possível. Para fazer isso, você precisa instalar o plug-in:
fastify.register(require('fastify-response-validation'));
A validação é flexível o suficiente. Por exemplo, a resposta de cada status será verificada de acordo com seu próprio esquema de validação.
O código relacionado à redação do artigo pode ser encontrado aqui .
Fontes adicionais de informação
1. blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators
2. habr.com/ru/company/dataart/blog/312638
apapacy@gmail.com
Maio 4 2021 anos