Search
Duplicate
🐈‍⬛

Fastify 핵심 개념 파헤치기

Created time
2024/02/28 08:16
Last edited time
2024/03/11 05:14
Status
Done
tag

들어가기에 앞서

참고한 자료를 바탕으로 비전문가가 정리한 글이므로 오류가 있을 수 있습니다.
오류에 대한 피드백은 언제든지 환영합니다. 부디 댓글로 알려주시길 바랍니다. 감사합니다.

Fastify와 Express.js

Fastify는 Node.js 기반의 웹 애플리케이션 및 API를 개발하는데 사용되는 웹 프레임워크입니다. 같은 Node.js 기반의 웹 프레임워크인 Express.js와는 달리, 이름 그대로 속도에 중점을 둔 프레임워크이며 Express.js보다 빠르고 경량화되어 있습니다. 하지만, 여전히 Express.js는 서버 개발에 있어서 안정적인 선택지 중 하나이며 10년이 넘는 역사를 가진만큼 Fastify보다 많은 자료를 보유하고 있고 커뮤니티 또한 좀 더 활성화되어 있습니다.

Fastify의 주요 기능과 개념

Plugin:

Fastify에서의 plugin은 기능을 모듈화하고 재사용 가능하도록 구성된 코드 블록입니다. 일반적으로 Fastify 애플리케이션의 특정 부분이나 기능을 확장하거나 추가하기 위해 사용됩니다. 자바스크립트의 대부분이 객체인 것처럼, 마찬가지로 plugin은 Fastify의 전부라고 할 수 있습니다.
plugin 생성
plugin을 생성할 때는 라우트, 유틸리티, 중첩 plugin 등록 등 원하는 무엇이든 정의하고 작성가능합니다. 단, 반드시 생성 후에는 done() 메소드를 호출해야 합니다.
// CommonJS 방식 module.exports = function (fastify, options, done) { fastify.get('/plugin', (request, reply) => { reply.send({ hello: 'world' }); done(); // 플러그인을 정의하고 나서 반드시 호출해야되는 메서드 } //ES module 방식 export default function plugin(fastify, options, done) { fastify.get('/plugin', (request, reply) => { reply.send({ hello: 'world' }); done(); // 플러그인을 정의하고 나서 반드시 호출해야되는 메서드 }); }
JavaScript
복사
fastify-plugin 모듈을 이용하는 방법도 있습니다. plugin을 빌드하고 해당 plugin을 컨텍스트 내에서 전역적으로 사용가능하게 해줍니다. 이 때문에 기본적으로 캡슐화가 보장되지 않지만, 옵션에서 encapsulate: true 를 설정하면 독립된 컨텍스트가 생성되며 캡슐화가 가능합니다.
또한, async/await를 사용하거나 Promise를 반환하는 경우 done() 콜백을 사용할 수 없습니다
import fp from 'fastify-plugin'; // callback함수 export default fp(function (fastify, opts, done) { // your plugin code done() }) // callback함수 : TypeScript export default fp((fastify: FastifyInstance, options: FastifyPluginOptions, done: (error?: FastifyError) => void): void => { }) // 비동기함수 export default fp(async function (fastify, opts) { // Wait for an async function to fulfill promise before proceeding await exampleAsyncFunction() }) // 비동기함수 : TypeScript fp(async (fastify: FastifyInstance, options: FastifyPluginOptions): Promise<void> => { })
TypeScript
복사
// 캡슐화 옵션 추가 예시 export default fp(async (server: FastifyInstance) => { server.get('/', async (request: FastifyRequest, reply: FastifyReply) => { const restaurant = { hello: 'world!' }; reply .code(200) .header('Content-type', 'application/json; charset=utf-8') .send(restaurant); } }), { name: 'routes', encapsulate: true, }, )
TypeScript
복사
plugin 등록
plugin은 register() API에 의해 fastify 인스턴스에 등록되며 fastify.register(plugin, [options]) 형태로 사용됩니다. register로 plugin을 등록하면 독립된 컨텍스트(plugin이 적용되는 범위) 생성되며 오직 그 범위 내에서만 plugin이 적용되므로, 캡슐화가 보장됩니다.
fastify.register(require('./my-get-plugin'),{ options } )
JavaScript
복사
// 동일 컨텍스트에 라우트 A와 B에 대한 요청은 onRequest 훅에서 // 로깅처리하고 라우트 C는 로깅처리를 안 하는 모습. // A,B의 컨텍스트와 C의 컨텍스트는 독립되어 있습니다. const loggedRoutes = async (fastify, opts) => { fastify.addHook("onRequest", (request, reply, done) => { console.log("Request Headers:", request.headers); done(); }); fastify.get("/routeA", async () => "Route A"); fastify.get("/routeB", async () => "Route B"); }; const normalRoutes = async (fastify, opts) => { fastify.get("/routeC", async () => "Route C"); }; fastify.register(loggedRoutes); fastify.register(normalRoutes);
TypeScript
복사

Decorator:

decorator는 Fatify 인스턴스에 유틸리티를 쉽게 추가해주는 API입니다. 기존 자바스크립트에서는 특정 기능을 가진 유틸리티를 작성한다면, export/import 방식으로 해당 유틸리티를 내보내고/사용 합니다.
// 더하기 함수 유틸리티 add.js export default function add(a, b) { return a + b; } // 더하기 함수 유틸리티 사용 import add from './add.js' console.log(add('that is ', 'awesome')); // that is awesome
TypeScript
복사
하지만, Fatify에서는 decorate API를 통해 해당 기능을 매우 쉽게 사용가능합니다.
fastify.decorate('add', (a, b) => a + b));
TypeScript
복사
위와 같이 fatify인스턴스에 ‘add’라는 유틸리티를 추가하고, 해당 인스턴스가 선언된 plugin을 register API를 사용하여 등록한다면 단순히 fastify.add 라고 작성만 해도 해당 기능을 언제든지 불러올 수 있습니다.
하지만 앞서 말씀드렸 듯이, plugin을 등록했을 때는 해당 플러그인에 대한 독립된 컨텍스트가 생성되어 오직 그 컨텍스트 안에서만 fastify.add 를 사용가능합니다.
import fp from 'fastify-plugin'; import { DataSource } from 'typeorm'; import { Restaurant } from '../modules/restaurant/entity'; export default fp(async fastify => { try { const connectDB = new DataSource({ type: 'postgres', entities: [__dirname + '/../src/modules/*/entity.ts', Restaurant], synchronize: true, url: process.env.TYPEORM_URL, }); connectDB .initialize() .then(() => console.log('DB가 연결되었습니다!')) .catch(err => console.error('DB 연결 실패!', err)); fastify.decorate('db', { restaurant: connectDB.getRepository(Restaurant), }); } catch (error) { console.log(error); } });
TypeScript
복사
위 코드는 TypeORM을 통해 DB를 연결하고, 해당 DB의 entity를 불러오는 plugin입니다.
fastify.decorate('db', { restaurant: connectDB.getRepository(Restaurant)}) 부분을 보시면 decorate를 사용하여 Restaurant 엔티티에 대한 테이블 정보를 db.restaurant에 매핑하고 있습니다. 이를 통해 단순히 fastify.db.restaurant 를 호출하는 것만으로 해당 테이블에 대한 데이터를 가져올 수 있습니다. 이 때, 해당 플러그인은 반드시 등록이 되어 있어야 합니다.
export default fp(async (server: FastifyInstance) => { // DB내 음식점 리스트 응답 server.get( '/restaurants', async (request: FastifyRequest, reply: FastifyReply) => { const restaurants = await server.db.restaurant.find(); console.log('test' + restaurants); reply .code(200) .header('Content-type', 'application/json; charset=utf-8') .header('Access-Control-Allow-Origin', '*') .send(restaurants); }, ); })
TypeScript
복사

Hook:

hook은 fastify.addhook 메소드를 통해 등록되며 애플리케이션과 request,reply 생명주기 내에서 발생되는 event를 감지 및 그에 관한 기능적 처리를 할 수 있게 합니다.
fastify.addHook('onClose', async fastify => { console.log('Running onClose hook...') const connection = getConnection(); if (connection) { await connection.close(); } console.log('Running onClose hook complete') });
TypeScript
복사
애플리케이션 라이프사이클:
onRoute: Fastify 인스턴스에 새로운 라우트를 추가할 때마다 트리거됩니다.
onRegister: 새로운 캡슐화된 컨텍스트(아래 설명)가 생성될 때마다 트리거됩니다.
onReady: 애플리케이션이 들어오는 HTTP 요청을 수신할 준비가 되었을 때 트리거됩니다.
onClose: 서버가 중지될 때 트리거됩니다.
request,reply 라이프사이클:
onRequest: 서버가 HTTP 요청을 받았을 때 트리거됩니다.
preParsing: 요청 본문이 평가되기 전에 트리거됩니다.
preValidation: Fastify에는 JSON 스키마 유효성 검사 개념이 있으며, 이에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 이 이벤트는 유효성 검사가 적용되기 전에 트리거됩니다.
preHandler: 각 라우트는 핸들러와 함께 등록되며, 이 이벤트는 해당 핸들러가 실행되기 전에 트리거됩니다.
preSerialization: 응답 페이로드가 문자열, 버퍼 등으로 변환되기 전에 트리거됩니다.
onError: 요청 라이프사이클 중에 오류가 발생한 경우 트리거됩니다.
onSend: 응답을 보내기 직전에 트리거됩니다.
onResponse: 요청이 서비스된 후에 트리거됩니다.

참고