Fastify 심층 분석: 고성능 Node.js 웹 프레임워크
Olivia Novak
Dev Intern · Leapcell

Fastify: 고성능 Node.js 웹 프레임워크
Fastify는 최적의 성능을 제공하도록 설계된 효율적이고 빠른 Node.js 웹 프레임워크입니다. 비교적 새로운 프레임워크임에도 불구하고 높은 성능과 낮은 오버헤드로 인해 많은 개발자들의 지지를 얻고 있습니다. Fastify는 간결한 개발 경험을 제공하며 빠른 라우팅과 플러그인 아키텍처를 지원하여 개발자가 애플리케이션을 쉽게 구축하고 확장할 수 있도록 합니다. Fastify 공식 웹사이트를 참조하면 Fastify를 다양한 측면에서 소개합니다.
1. 주요 기능 및 원칙
- 고성능: Fastify는 가장 빠른 웹 프레임워크 중 하나입니다. 코드 복잡성에 따라 초당 최대 30,000개의 요청을 처리할 수 있습니다.
- 확장성: Fastify는 훅, 플러그인 및 데코레이터의 도움으로 완벽하게 확장 가능합니다.
- 스키마 기반: 필수는 아니지만 JSON 스키마를 사용하여 경로를 검증하고 출력을 직렬화하는 것이 좋습니다. Fastify는 스키마를 고성능 함수로 컴파일합니다.
- 로깅: 최고의 로거인 Pino를 선택하면 로깅 비용을 거의 없앨 수 있습니다.
- 개발자 친화적: 이 프레임워크는 성능과 보안을 희생하지 않으면서 개발자의 일상적인 사용에 매우 표현력이 뛰어나고 편리합니다.
- TypeScript 지원: 증가하는 TypeScript 커뮤니티를 지원하기 위해 TypeScript 타입 선언 파일을 유지하려고 노력합니다.
2. 탄생 배경
Fastify가 등장하기 전에는 Express와 Koa가 Node.js 분야에서 널리 사용되는 두 가지 프레임워크였습니다. 인기가 많고 사용하기 쉽지만 많은 수의 요청을 처리할 때 성능이 최적화되지 않습니다. Fastify는 다음과 같은 주요 문제를 해결하는 것을 목표로 합니다.
- 성능: 각 요청의 오버헤드를 줄여 기존 프레임워크보다 더 높은 성능을 제공하고 더 많은 요청을 처리할 수 있으므로 고성능 애플리케이션을 구축하는 데 매우 중요합니다.
- 개발 효율성: 플러그인 아키텍처와 즉시 사용 가능한 기능(예: 스키마 유효성 검사, 로깅 등)을 통해 효율적이고 사용하기 쉬운 개발 환경을 제공하여 개발 프로세스를 가속화하고 잠재적인 오류를 줄입니다.
- 스키마 유효성 검사: 클라이언트 입력을 검증할 뿐만 아니라 애플리케이션의 데이터 일관성 및 신뢰성을 보장하는 JSON 스키마에 대한 기본 지원 기능이 있습니다. 이는 많은 프레임워크가 핵심 수준에서 통합하지 않는 기능입니다.
- 확장성: 플러그인 아키텍처를 통해 개발자는 애플리케이션의 전체 성능에 큰 영향을 주지 않고 새로운 기능을 쉽게 추가하거나 타사 라이브러리를 통합할 수 있습니다.
Fastify는 위에서 설명한 문제를 해결함으로써 고성능, 유지 관리 가능하고 개발하기 쉬운 Node.js 애플리케이션을 개발하기 위한 강력한 플랫폼을 제공하며 JavaScript 커뮤니티에서 빠르게 주목을 받고 사용량이 증가하고 있습니다.
3. 고성능 구현
Fastify의 고성능의 핵심은 각 요청의 오버헤드를 최소화하는 데 도움이 되는 설계 및 아키텍처 선택에 있습니다.
- 효율적인 라우팅 배포: Fastify는 빠른 라우팅 배포 메커니즘을 채택합니다. 요청이 들어오면 호출할 처리 기능을 빠르게 결정하여 요청 처리 시간을 크게 줄일 수 있습니다.
- 사전 컴파일된 직렬화: 응답을 보내기 전에 Fastify는 런타임에 객체를 동적으로 직렬화하는 대신 사전 컴파일된 직렬화 함수를 사용하여 응답 직렬화 속도를 높이고 클라이언트로 보냅니다.
- 스키마 기반 개발: Fastify는 JSON 스키마를 사용하여 경로의 입력 및 출력을 정의할 것을 강력히 권장합니다(경우에 따라 필요함). 이는 API를 검증하고 문서화하는 데 도움이 될 뿐만 아니라 Fastify가 유효성 검사 로직을 미리 컴파일하여 런타임 효율성을 향상시킬 수 있도록 합니다.
- 최적화된 플러그인 시스템: Fastify의 플러그인 아키텍처는 효율적으로 설계되어 코드 재사용 및 모범 사례를 지원하는 동시에 핵심 프레임워크를 가볍게 유지하여 성능 저하 없이 높은 수준의 모듈성과 유연성을 달성합니다.
- 효율적인 로깅: 내장된 로깅 도구인 Pino는 속도를 위해 설계되었으며 애플리케이션 성능을 유지하는 데 중요한 매우 낮은 오버헤드로 로깅 기능을 제공합니다.
- 제로 비용 추상화: Fastify의 설계 철학은 추상화 계층의 성능 오버헤드를 최소화하는 것입니다. 다양한 추상화 및 편리한 기능을 사용하더라도 높은 성능을 유지할 수 있습니다.
이러한 설계 결정 및 최적화를 통해 Fastify는 뛰어난 성능을 제공하며 효율적이고 응답성이 뛰어난 Node.js 애플리케이션을 구축하기 위한 이상적인 선택입니다.
4. 설치 및 사용법
4.1 npm을 통해 Fastify 설치
npm install fastify
4.2 간단한 예제 코드
// 프레임워크를 가져오고 인스턴스화합니다. import Fastify from "fastify"; const fastify = Fastify({ logger: true, }); // 경로 선언 fastify.get("/", async function handler(request, reply) { return { hello: "world" }; }); // 서버 실행! try { await fastify.listen({ port: 3000 }); } catch (err) { fastify.log.error(err); process.exit(1); }
4.3 cli를 사용하여 스켈레톤 생성
npm install --global fastify-cli fastify generate newproject
4.4 JSON 스키마 및 훅을 사용하여 요청 처리 예제
import Fastify from "fastify"; const fastify = Fastify({ logger: true, }); fastify.route({ method: "GET", url: "/", schema: { // 요청에 `name` 매개변수가 있는 쿼리 문자열이 필요합니다. querystring: { type: "object", properties: { name: { type: "string" }, }, required: ["name"], }, // 응답은 'string' 유형의 'hello' 속성이 있는 객체여야 합니다. response: { 200: { type: "object", properties: { hello: { type: "string" }, }, }, }, }, // 이 함수는 핸들러가 실행되기 전에 모든 요청에 대해 실행됩니다. preHandler: async (request, reply) => { // 예: 인증 확인 }, handler: async (request, reply) => { return { hello: "world" }; }, }); try { await fastify.listen({ port: 3000 }); } catch (err) { fastify.log.error(err); process.exit(1); }
5. 플러그인, 데코레이터, 미들웨어, 훅
Fastify에서 플러그인, 데코레이터, 미들웨어, 훅은 프레임워크의 핵심 개념이며 각각 다른 역할을 합니다.
5.1 플러그인
플러그인은 Fastify 애플리케이션에서 기능을 추가하고, 코드를 공유하거나, 로직을 캡슐화하는 주요 방법입니다. 플러그인은 Fastify 인스턴스, 옵션 및 콜백 함수를 매개변수로 사용하는 함수일 수 있습니다. 플러그인은 경로를 등록하고, 데코레이터를 추가하고, 새 훅을 선언하고, 다른 플러그인을 캡슐화할 수도 있으며 모듈식 애플리케이션을 구축하는 데 사용됩니다. 개발자는 이를 사용하여 재사용 가능한 로직 블록을 구축하고 다른 Fastify 애플리케이션 또는 동일한 애플리케이션의 다른 부분에서 공유할 수 있습니다.
5.2 데코레이터
데코레이터는 Fastify 인스턴스, 요청(Request) 및 응답(Reply) 객체를 확장하는 데 사용됩니다. 새 메서드나 속성을 추가하여 개발자는 사용자 지정 함수 또는 데이터를 추가하고 애플리케이션의 여러 부분에서 사용할 수 있도록 만들 수 있습니다. 예를 들어, 각 요청에 대해 공유 구성 데이터 또는 서비스에 액세스하는 메서드를 추가하기 위해 데코레이터를 추가할 수 있습니다.
5.3 미들웨어
Fastify는 미들웨어에 의존하도록 설계되지는 않았지만 특정 기능의 호환성 또는 통합을 위해 Express/Connect 스타일 미들웨어 사용을 지원합니다. 미들웨어는 요청 및 응답 객체에 액세스하고, 코드를 실행하고, 요청 및 응답 객체를 수정하고, 요청 처리 체인을 종료하거나, 호출 스택에서 다음 미들웨어를 호출할 수 있습니다. Fastify 미들웨어를 사용할 때는 부적절하게 사용하면 Fastify의 일부 최적화를 우회하고 성능에 영향을 미칠 수 있으므로 주의해야 합니다.
5.4 훅
훅은 Fastify의 메커니즘으로, 개발자가 요청 수신 후, 경로가 확인되기 전, 응답이 전송되기 전 등 요청 라이프사이클의 여러 단계에서 로직을 중재하고 실행할 수 있도록 합니다. 훅은 권한 확인, 요청 로깅, 응답 수정 등과 같은 전처리 또는 후처리 로직을 실행하는 데 사용할 수 있습니다. Fastify는 onRequest, preHandler, onSend 등과 같은 다양한 유형의 훅을 제공하여 개발자가 요청 처리의 다양한 단계를 세밀하게 제어할 수 있습니다.
이러한 구성 요소는 함께 작동하여 프레임워크의 고성능 특성을 유지하면서 Fastify에 뛰어난 유연성과 확장성을 제공합니다.
5.5 라이프사이클 가중치
Fastify의 구성 요소(플러그인, 데코레이터, 미들웨어 및 훅)는 특정 실행 순서와 우선순위를 따르며, 이는 요청 처리 흐름에서 작업 시기를 결정합니다. 이 실행 흐름을 이해하는 것은 효율적이고 안정적인 Fastify 애플리케이션을 설계하는 데 매우 중요합니다.
- 플러그인: 애플리케이션이 시작될 때 로드되고 등록 순서대로 실행됩니다. 애플리케이션이 시작된 후 플러그인 설정이 고정되고 플러그인에 정의된 기능(예: 경로, 훅, 데코레이터)은 이후의 각 요청에 사용할 수 있습니다.
- 데코레이터: 명확한 실행 시간이 없습니다. 데코레이터된 객체(Fastify 인스턴스, 요청, 응답)에 추가된 메서드 또는 속성은 즉시 사용할 수 있으며 객체의 전체 라이프사이클 동안 유효합니다.
- 미들웨어: 각 요청의 처리 흐름 초기에, 특히 경로 일치 전에 실행됩니다. 미들웨어는 요청 및 응답 객체를 수정하거나 요청을 다음 프로세서로 전달할지 여부를 결정할 수 있습니다.
- 훅: 특정 실행 순서를 따르며 요청 처리 흐름에 반영됩니다.
- onRequest: 다른 처리 전에 요청을 받은 직후에 실행됩니다.
- preParsing: 요청 본문을 구문 분석하기 전에 실행됩니다.
- preValidation: 경로 수준 유효성 검사 전에 실행됩니다.
- preHandler: 경로 처리 기능 전에 실행됩니다.
- preSerialization: 응답을 클라이언트로 보내기 전에 응답을 직렬화하기 전에 실행됩니다.
- onSend: 응답이 클라이언트로 전송되기 전에, 직렬화 후에 실행됩니다.
- onResponse: 응답이 클라이언트로 완전히 전송된 후에 실행됩니다.
중단 기능
- 플러그인: 요청 처리 중단에 직접적으로 관여하지 않지만 프로세스에 영향을 미치는 훅 또는 미들웨어를 등록할 수 있습니다.
- 데코레이터: 프로세스를 제어하지 않으므로 중단에 관여하지 않습니다.
- 미들웨어: 요청 처리 흐름을 중단할 수 있습니다. 예를 들어, next()를 호출하지 않거나 응답을 보내지 않으면 후속 처리를 중지할 수 있습니다.
- 훅: 특정 훅(예: preHandler)은 요청 처리를 계속할지 또는 직접 응답을 보낼지 여부를 결정하여 후속 프로세스를 중단할 수 있습니다.
이러한 구성 요소의 실행 순서와 중단 기능을 이해하는 것은 예상대로 작동하는 Fastify 애플리케이션을 구축하는 데 매우 중요합니다.
6. 라이프사이클
일반적인 프로세스
- [들어오는 요청]: 새 요청이 시스템에 들어옵니다.
- ↓
- [라우팅]: 요청에 해당하는 경로를 결정합니다.
- ↓
- [인스턴스 로거]: 요청과 관련된 인스턴스 로그를 기록합니다.
- ↓
- [onRequest 훅]:
onRequest
훅 함수를 실행하고, 통합된 요청 전처리에 사용할 수 있습니다. - ↓
- [preParsing 훅]: 요청 본문을 구문 분석하기 전에 훅 함수를 실행합니다.
- ↓
- [구문 분석]: 요청 본문을 구문 분석합니다.
- ↓
- [preValidation 훅]: 경로 유효성 검사 전에 훅 함수를 실행합니다.
- ↓
- {유효성 검사}: 경로 유효성 검사를 수행합니다.
- 유효성 검사가 실패하면→[400]: 400 오류 응답을 반환합니다.
- 유효성 검사가 통과하면→↓
- [preHandler 훅]: 경로 처리 함수 전에 훅 함수를 실행하고, 권한 확인과 같은 작업을 수행할 수 있습니다.
- ↓
- [사용자 핸들러]: 사용자 정의 경로 처리 함수를 실행합니다.
- ↓
- [응답]: 응답 콘텐츠를 준비합니다.
- ↓
- [preSerialization 훅]: 응답을 직렬화하기 전에 훅 함수를 실행합니다.
- ↓
- [onSend 훅]: 응답을 보내기 전에 훅 함수를 실행합니다.
- ↓
- [나가는 응답]: 응답을 보냅니다.
- ↓
- [onResponse 훅]: 응답이 완전히 전송된 후에 훅 함수를 실행합니다.
7. 캡슐화
Fastify의 캡슐화 컨텍스트
는 기본 기능 중 하나입니다. 캡슐화 컨텍스트는 경로에 사용할 수 있는 데코레이터, 등록된 훅 및 플러그인을 결정합니다. 각 컨텍스트는 부모 컨텍스트에서만 상속받고 부모 컨텍스트는 하위 컨텍스트의 엔터티에 액세스할 수 없습니다. 기본 상황이 요구 사항을 충족하지 않으면 fastify-plugin을 사용하여 캡슐화 컨텍스트를 깨고 하위 컨텍스트에 등록된 콘텐츠를 연결된 부모 컨텍스트에서 사용할 수 있도록 할 수 있습니다.
8. 경로에 대한 JSON 스키마 유효성 검사
JSON 스키마는 Fastify에서 경로와 결합되어 클라이언트 요청 데이터를 검증하고 응답 데이터를 포맷합니다. JSON 스키마를 정의하여 들어오는 요청 데이터가 특정 포맷 및 유형 요구 사항을 충족하는지 확인하고 동시에 응답 데이터의 구조를 제어하여 API의 견고성과 보안을 향상시킬 수 있습니다.
예제 코드
const fastify = require("fastify")(); // JSON 스키마 정의 const userSchema = { type: "object", required: ["username", "email", "gender"], properties: { username: { type: "string" }, email: { type: "string", format: "email" }, gender: { type: "string", minimum: "male" }, }, }; fastify.route({ method: "POST", url: "/create-user", schema: { body: userSchema, // 요청 본문을 검증하기 위해 JSON 스키마 사용 response: { 200: { type: "object", properties: { success: { type: "boolean" }, id: { type: "string" }, }, }, }, }, handler: (request, reply) => { // 요청 로직 처리 // 사용자 생성이 성공적이고 사용자 ID를 반환한다고 가정 reply.send({ success: true, id: "leapcell" }); }, }); fastify.listen(3000, (err) => { if (err) throw err; console.log("Server is running on http://localhost:3000"); });
이 예제에서:
userSchema
는 필수 필드 및 각 필드의 유형을 포함하여 요청 본문의 예상 포맷을 설명하는 정의됩니다.- 경로 구성에서
userSchema
는schema
속성을 통해 요청 본문(body
)에 적용되며 Fastify는 들어오는 요청 데이터가 정의된 스키마를 준수하는지 자동으로 검증합니다. - 응답의
schema
는 응답 데이터 구조가 예상과 일치하는지 확인하기 위해 정의됩니다.
이러한 방식으로 들어오는 모든 요청이 엄격하게 검증되고 모든 응답이 预定格에 부합하는지 확인할 수 있어 API의 견고성과 사용자에 대한 예측 가능성이 향상됩니다.
9. 예제
9.1 훅의 로그인 상태 문제 처리
const fastify = require("fastify")({ logger: true }); const secureSession = require("fastify-secure-session"); // 세션 플러그인 등록 fastify.register(secureSession, { secret: "averylognsecretkey", // 길고 복잡한 비밀 키를 사용해야 합니다. cookie: { path: "/", // 필요에 따라 다른 쿠키 옵션 구성 }, }); // /api/a 및 /api/c에만 preHandler 훅 적용 fastify.addHook("preHandler", (request, reply, done) => { if ( (request.routerPath === "/api/1" || request.routerPath === "/api/2") && !request.session.user ) { request.session.redirectTo = request.raw.url; reply.redirect(302, "/login"); done(); } else { done(); } }); // 세션 확인이 필요하지 않은 경로 fastify.get("/api/2", (req, reply) => { reply.send({ message: "/api/2에 대한 액세스 권한 부여됨" }); }); // 세션 확인이 필요한 경로 fastify.get("/api/1", (req, reply) => { reply.send({ message: "/api/1에 대한 액세스 권한 부여됨" }); }); fastify.get("/api/3", (req, reply) => { reply.send({ message: "/api/3에 대한 액세스 권한 부여됨" }); }); // 로그인 페이지에 대한 경로 fastify.get("/login", (req, reply) => { reply.send(`로그인 양식이 여기에 있습니다. 인증하려면 /login에 POST하십시오.`); }); // 로그인 로직 fastify.post("/login", (req, reply) => { const { username, password } = req.body; if (username === "user" && password === "password") { req.session.user = username; const redirectTo = req.session.redirectTo || "/"; delete req.session.redirectTo; reply.redirect(302, redirectTo); } else { reply.code(401).send({ error: "Unauthorized" }); } }); // 서버 시작 fastify.listen(3000, (err) => { if (err) { fastify.log.error(err); process.exit(1); console.log(`서버가 http://localhost:3000에서 수신 대기 중입니다.`); });
9.2 매개변수 제한
다음 예제에서는 a
및 b
매개변수가 post 요청으로 전달되어야 합니다.
const fastify = require("fastify")(); const postSchema = { schema: { body: { type: "object", required: ["a", "b"], properties: { a: { type: "string" }, b: { type: "string" } } }, response: { // 누락된 경우 기본적으로 400 오류가 반환되고 여기에서 400의 반환 유형을 수정할 수 있습니다. 400: { type: "object", properties: { statusCode: { type: "number" }, error: { type: "string" }, message: { type: "string" } } } }, // 여기에서 위의 기본 가로채기 전략을 가로챌 수 있습니다. preValidation: (request, reply, done) => { if (!request.body ||!request.body.a ||!request.body.b) { reply .code(400) .send({ error: "Bad Request", message: "필수 매개변수가 누락되었습니다." }); return; } done(); } }; fastify.post("/api/data", postSchema, (request, reply) => { // 여기에 도달하면 a 및 b 매개변수가 모두 확인을 통과했음을 의미합니다. reply.send({ message: "성공" }); }); fastify.listen(3000, (err) => { if (err) { console.error(err); process.exit(1); } console.log("3000 포트에서 서버 수신 중"); });
경로에서 preValidation
훅과 사용자 지정 response schema
를 정의하지 않으면 POST 요청의 body
가 정의된 JSON 스키마를 준수하지 않을 때 Fastify는 기본적으로 400 오류(잘못된 요청)를 반환합니다. 이 오류의 응답 본문에는 어떤 필드가 요구 사항을 충족하지 않는지에 대한 정보가 포함되지만 이 정보는 Fastify에서 자동으로 생성되며 사용자 지정 오류 메시지만큼 구체적이거나 명확하지 않을 수 있습니다. 기본적으로 a
매개변수만 전달되고 b
매개변수가 누락된 경우 Fastify는 다음과 유사한 오류 세부 정보가 포함된 응답을 반환합니다.
{ "statusCode": 400, "error": "Bad Request", "message": "body should have required property 'b'" }
Leapcell: 최고의 서버리스 웹 호스팅
마지막으로 NodeJS 서비스를 배포하는 데 가장 적합한 플랫폼인 **Leapcell**을 추천합니다.
🚀 좋아하는 언어로 빌드
JavaScript, Python, Go 또는 Rust로 손쉽게 개발하세요.
🌍 무료로 무제한 프로젝트 배포
사용한 만큼만 지불하세요. 요청도, 요금도 부과되지 않습니다.
⚡ 사용한 만큼 지불, 숨겨진 비용 없음
유휴 요금이 없고 원활한 확장성만 제공됩니다.
🔹 Twitter에서 팔로우하세요: @LeapcellHQ