Sungguh memuaskan bahwa sekarang, akhirnya, ada pesaing yang layak untuk tempat kerangka web utama untuk semua orang dan segalanya - maksud saya bukan Fastify.js, tetapi, tentu saja, Nest.js. Meskipun dalam hal indikator kuantitatif popularitas, itu sangat-sangat jauh dari Express.js.
Meja. Metrik popularitas paket dari npmjs.org, github.com
| Tidak. | Paket | Jumlah unduhan | Jumlah "bintang" |
|---|---|---|---|
| satu | Menghubung | 4 373 963 | 9100 |
| 2 | mengekspresikan | 16492 569 | 52.900 |
| 3 | koa | 844 877 | 31.100 |
| empat | nestjs | 624603 | 36.700 |
| lima | hapi | 389 530 | 13.200 |
| 6 | fastify | 216 240 | 18.600 |
| 7 | restify | 93.665 | 10 100 |
| delapan | polka | 71 394 | 4.700 |
Express.js masih berfungsi di lebih dari 2/3 aplikasi web node.js. Selain itu, 2/3 dari kerangka kerja web paling populer untuk node.js menggunakan pendekatan Express.js. (Akan lebih akurat untuk mengatakan, pendekatan pustaka Connect.js, yang menjadi dasar Express.js sebelum versi 4).
Posting ini membahas fitur-fitur kerangka kerja web utama untuk node.js, dan apa yang membuat Fastify.js tingkat kerangka kerja yang berbeda, yang memungkinkan Anda untuk memilihnya sebagai kerangka kerja untuk mengembangkan proyek Anda berikutnya.
Kritik kerangka kerja berdasarkan middleware sinkron
Apa yang salah dengan kode semacam ini?
app.get('/', (req, res) => {
res.send('Hello World!')
})
1. Fungsi yang memproses rute tidak mengembalikan nilai. Sebaliknya, Anda harus memanggil salah satu metode pada objek response (res). Jika metode ini tidak dipanggil secara eksplisit, bahkan setelah fungsi tersebut kembali, klien dan server akan tetap dalam keadaan menunggu respons server hingga setiap waktu tunggu habis. Ini hanya “kerugian langsung”, tetapi ada juga “keuntungan yang hilang”. Fakta bahwa fungsi ini tidak mengembalikan nilai membuatnya tidak mungkin untuk hanya mengimplementasikan fungsionalitas yang diminta, misalnya, validasi atau pencatatan respons yang dikembalikan ke klien.
2. Di Express.js, penanganan error bawaan selalu sinkron. Namun, jarang ada rute yang dilakukan tanpa panggilan ke operasi asinkron. Karena Express.js dibangun di era pra-industri, penangan kesalahan sinkron standar untuk kesalahan asinkron tidak akan berfungsi, dan kesalahan asinkron harus ditangani seperti ini:
app.get('/', async (req, res, next) => {
try {
...
} catch (ex) {
next(ex);
}
})
atau seperti ini:
app.get('/', (req, res, next) => {
doAsync().catch(next)
})
3. Kompleksitas inisialisasi asynchronous layanan. Misalnya, aplikasi bekerja dengan database dan mengakses database sebagai layanan dengan menyimpan referensi dalam variabel. Inisialisasi rute Express.js selalu sinkron. Ini berarti bahwa ketika permintaan klien pertama mulai tiba di rute, inisialisasi asinkron layanan, kemungkinan besar, tidak akan punya waktu untuk bekerja, jadi Anda harus "menyeret" kode asinkron ke rute untuk mendapatkan tautan ke layanan ini. Semua ini, tentu saja, dapat direalisasikan. Tapi itu terlalu jauh dari kesederhanaan naif dari kode asli:
app.get('/', (req, res) => {
res.send('Hello World!')
})
4. Dan akhirnya, yang terakhir tapi tidak kalah pentingnya. Sebagian besar aplikasi Express.js menjalankan sesuatu seperti ini:
app.use(someFuction);
app.use(anotherFunction());
app.use((req, res, nexn) => ..., next());
app.get('/', (req, res) => {
res.send('Hello World!')
})
Ketika Anda mengembangkan bagian aplikasi Anda, Anda dapat yakin bahwa 10-20 middleware telah bekerja sebelum kode Anda, yang menggantung semua jenis properti pada objek req, dan bahkan dapat mengubah permintaan asli, persis seperti pada kenyataannya bahwa jumlah yang sama jika tidak ada lebih banyak middleware yang dapat ditambahkan setelah Anda mengembangkan bagian aplikasi. Meskipun, dalam dokumentasi Express.js, objek res.locals direkomendasikan secara ambigu untuk melampirkan properti tambahan:
// Express.js
app.use(function (req, res, next) {
res.locals.user = req.user
res.locals.authenticated = !req.user.anonymous
next()
})
Upaya historis untuk mengatasi kekurangan Express.js
Tidak mengherankan, penulis utama Express.js dan Connect.js - TJ Holowaychuk - meninggalkan proyek untuk mulai mengembangkan kerangka kerja Koa.js yang baru. Koa.js menambahkan asynchrony ke Express.js. Misalnya, kode ini menghilangkan kebutuhan untuk menangkap kesalahan asinkron dalam kode setiap rute dan menempatkan penangan dalam satu 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
};
}
})
Versi Koa.js yang paling awal bermaksud memperkenalkan generator untuk menangani panggilan asinkron:
// 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;
});
Pengenalan async / await meniadakan kegunaan bagian Koa.js ini, dan sekarang tidak ada contoh seperti itu bahkan dalam dokumentasi framework.
Hampir seusia dengan Express.js - kerangka kerja Hapi.js. Pengontrol di Hapi.js sudah mengembalikan nilai, yang merupakan langkah maju dari Express.js. Tidak mendapatkan popularitas yang sebanding dengan Express.js, sebuah komponen dari proyek Hapi.js - perpustakaan Joi, yang memiliki 3.388.762 unduhan dari npmjs.org, dan sekarang digunakan baik di backend maupun di frontend, telah menjadi sangat sukses. Menyadari bahwa validasi objek yang masuk bukanlah kasus khusus, tetapi atribut yang diperlukan dari setiap aplikasi - validasi di Hapi.js dimasukkan sebagai bagian dari kerangka kerja, dan sebagai parameter dalam definisi rute:
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)
})
}
}
});
Saat ini, perpustakaan Joi adalah proyek mandiri.
Jika kita telah mendefinisikan skema validasi objek, maka kita telah mendefinisikan objek itu sendiri. Ada sedikit yang tersisa untuk membuat rute pendokumentasian sendiri di mana perubahan dalam skema validasi data mengubah dokumentasi, sehingga dokumentasi selalu cocok dengan kodenya.
Sejauh ini, salah satu solusi terbaik dalam dokumentasi API adalah swagger / openAPI. Akan sangat mudah jika skema, deskripsi yang mempertimbangkan persyaratan swagger / openAPI, dapat digunakan baik untuk validasi maupun untuk menghasilkan dokumentasi.
Fastify.js
Izinkan saya meringkas persyaratan yang tampaknya penting bagi saya saat memilih kerangka kerja web:
- ( ).
- .
- .
- / .
- .
- .
Semua poin ini sesuai dengan Nest.js, yang saat ini saya kerjakan di beberapa proyek. Fitur Nest.js adalah penggunaan dekorator secara luas, yang dalam beberapa kasus dapat menjadi batasan jika persyaratan teknis menentukan penggunaan JavaScript standar (dan seperti yang Anda ketahui, dengan standarisasi dekorator di JavaScript, situasi ini terhenti beberapa bertahun-tahun yang lalu, dan tampaknya tidak akan segera menemukan penyelesaiannya) ...
Oleh karena itu, alternatifnya bisa berupa kerangka Fastify.js, fitur yang sekarang akan saya analisis.
Fastify.js mendukung gaya menghasilkan respons server yang akrab bagi pengembang Express.js, dan lebih menjanjikan dalam bentuk nilai pengembalian fungsi, sambil meninggalkan kemampuan untuk secara fleksibel memanipulasi parameter respons lainnya (status, header):
// 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}
})
Penanganan error dapat dilakukan secara built-in (out of the box) dan custom.
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' };
});
}
Kedua opsi - sinkron dan asinkron - ditangani dengan cara yang sama oleh penangan kesalahan bawaan. Tentu saja, selalu ada sedikit kemampuan bawaan. Mari kita sesuaikan penangan kesalahan:
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' };
});
Bagian kode ini disederhanakan (error melempar literal). Demikian pula, Anda dapat membuat kesalahan khusus. (Mendefinisikan kesalahan serial yang dapat disesuaikan adalah topik terpisah, jadi tidak ada contoh yang diberikan).
Untuk validasi, Fastify.js menggunakan perpustakaan Ajv.js, yang mengimplementasikan antarmuka swagger / openAPI. Fakta ini memungkinkan untuk mengintegrasikan Fastify.js dengan swagger / openAPI dan mendokumentasikan API sendiri.
Secara default, validasi tidak ketat (kolom bersifat opsional dan kolom yang tidak ada dalam skema diperbolehkan). Untuk membuat validasi ketat, perlu untuk menentukan parameter dalam konfigurasi Ajv, dan dalam skema validasi:
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' };
});
}
Karena skema objek yang masuk telah ditentukan, membuat dokumentasi swagger / openAPI tergantung pada penginstalan 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,
});
Validasi respons juga dimungkinkan. Untuk melakukan ini, Anda perlu menginstal plugin:
fastify.register(require('fastify-response-validation'));
Validasi cukup fleksibel. Misalnya, respons dari setiap status akan diperiksa sesuai dengan skema validasinya sendiri.
Kode yang terkait dengan penulisan artikel dapat ditemukan di sini .
Sumber informasi tambahan
1. blog.stevensanderson.com/2013/12/21/experiments-with-koa-and-javascript-generators
2. habr.com/ru/company/dataart/blog/312638
apapacy@gmail.com
Mei 4 2021 tahun