[ 살펴보기 ] Fastify - Routing
![[ 살펴보기 ] Fastify - Routing](https://cdn.hashnode.com/res/hashnode/image/upload/v1730021077362/a4eebe3d-4f32-4e00-ae32-096b4e57875c.jpeg)
Fastify에선 다음 세 가지 방법으로 route를 추가할 수 있다.
먼저 fastify instance의 route method를 통해 필요한 option과 router handler를 모두 설정하는 방법이 있다.
fastify.route({
method: 'GET',
url: '/',
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})
보다 simple한 방법은 fastify instance에서 제공하는 각각의 http method를 통해 route를 추가하는 방법이다.
const options = {
...
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
}
fastify.get('/', options)
마지막 방법은 위의 방법과 유사하지만 route handler를 router option 객체에 포함하지 않고 별도로 전달하는 방법이다.
const options = {
...
}
fastify.get('/', options, (request, reply) => {
reply.send({ hello: 'world' })
})
두 번째 parameter인 options은 필수 값이 아니므로 options을 따로 설정할 필요가 없으면 두 번째 parameter로 바로 router handler를 추가해도 무방하다.
fastify.get("/", async (request, reply) => {
reply.send({ hello: 'world' })
});
해당 포스트는 마지막 방법인 route handler를 따로 전달하는 방법으로 fastify route를 살펴본다. 테스트를 위해 사용되는 project 기본 세팅은 다음과 같다.
app.js
import Fastify from "fastify";
const fastify = Fastify({
logger: false,
});
fastify.get("/", (request, reply) => {
reply.send({ hello: "world" });
});
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
package.json
{
"name": "fastify-test",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"start": "nodemon app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"fastify": "^5.0.0",
"nodemon": "^3.1.7"
}
}
위의 설정을 기준으로 application의 실행은 npm run start command를 통해 가능하다.
Get methods route
간단한 get method route는 다음과 같이 설정할 수 있다. handler의 첫 번째 parameter를 통해 client request 정보를 담고 있는 object에 접근할 수 있고 reply의 send method를 통해 client에게 response를 전달한다.
fastify.get("/", (request, reply) => {
console.log(request.headers);
reply.send({ hello: "world" });
});
만약 member/1이나 member/2와 같이 dynamic route url을 위한 route가 필요하다면 다음과 같이 추가할 수 있다.
fastify.get("/member/:memberId", (request, reply) => {
const { memberId } = request.params;
reply.send({ hello: "world" });
});
위에 예제와 같이 route가 설정된 상태로 member/1url으로 요청을 보내면 memberId는 1이 되고 member/2url로 요청으로 보내면 memberId는 2가 된다.
Post method route
Post method route를 추가하기 위해서 다음과 같이 설정할 수 있다.
fastify.post("/member", async (request, reply) => {
console.log(request.body);
const newMemberRequest = request.body;
const newMember = await createMember(newMemberRequest);
return reply.code(201).send(newMember);
});
Post method를 통한 request는 보통 새로운 resource 생성에 대한 request이므로 request object의 body property를 통해 client request body을 조회할 수 있다.
또한 위의 예제에서 볼 수 있듯이 reply의 code method를 통해 response의 status code를 설정할 수 있다. send method를 통해 resonse를 전달할 때 code method를 명시적으로 설정해주지 않으면 default status는 200이다.
Patch method route
Patch나 put method는 보통 특정 자원에 대한 수정 요청을 나타내므로 위에서 살펴본 dynamic route에 대한 요청을 받을 수 있도록 route를 추가해야 한다.
fastify.patch("/member/:memberId", async (request, reply) => {
const { memberId } = request.params;
const updateMemberRequest = request.body;
const updatedMember = await updateMember(memberId, updateMemberRequest);
return reply.send(updatedMember);
});
위의 예제에선 request.params을 통해 url에 포함된 member id를 조회하고 request.body를 통해 client가 request로 보낸 body를 조회한다.
그리고 member id와 reqeust body를 통해 기존 자원을 수정하고 수정 결과를 client에게 전달한다.
Delete method route
Delete method 역시 특정 자원에 대한 삭제 요청을 나타내므로 dynamic route에 대한 요청을 받을 수 있도록 route를 추가한다.
fastify.delete("/member/:memberId", async (request, reply) => {
const { memberId } = request.params;
const updatedMember = await deleteMember(memberId);
return reply.code(204).send();
});
위의 예제에선 request.params을 통해 url에 포함된 member id를 조회하고 deleteMember를 통해 전달된 memberId에 해당하는 user를 삭제한다.
위에서 살펴본 post, patch, delete와 같이 route handler에 async keyword를 사용한다면 reply없이 바로 결과 값을 return해도 무방하다.
fastify.patch("/member/:memberId", async (request, reply) => {
const { memberId } = request.params;
const updateMemberRequest = request.body;
const updatedMember = await updateMember(memberId, updateMemberRequest);
return updatedMember;
});
하지만 async를 사용하는 route handler에서 reply를 통해 response를 전달한다면 return을 추가하여 race condition이 발생할 수 있는 상황을 방지해주는 것이 좋다. ( Reference - Fastify - Async Await )
fastify.patch("/member/:memberId", async (request, reply) => {
const { memberId } = request.params;
const updateMemberRequest = request.body;
const updatedMember = await updateMember(memberId, updateMemberRequest);
return reply.send(updatedMember);
});
Register Routes
route를 구성할 때는 보통 CRUD ( CREATE, READ, UPDATE, DELETE ) reqeust를 처리하는 route를 특정 resource별로 구분하여 구성한다. 우선 member관련 route 구성을 위해 routes 폴더를 생성하고 routes 폴더 안에 member.js 파일을 생성한다.
// member.js
const memberRoutes = (fastify, options) => {
fastify.get("/member", opts, (request, reply) => {
reply.send({ hello: "world" });
});
fastify.post("/member", async (request, reply) => {
const newMemberRequest = request.body;
const newMember = await createMember(newMemberRequest);
return reply.code(201).send(newMember);
});
fastify.patch("/member/:memberId", async (request, reply) => {
const { memberId } = request.params;
const updateMemberRequest = request.body;
const updatedMember = await updateMember(memberId, updateMemberRequest);
return reply.send(updatedMember);
});
fastify.delete("/member/:memberId", async (request, reply) => {
const { memberId } = request.params;
await deleteMember(memberId);
return reply.code(204).send();
});
};
export default memberRoutes;
위와 같이 생성한 route는 다음과 같이 등록할 수 있다.
import Fastify from "fastify";
import memberRoutes from "./routes/member.js";
const fastify = Fastify({
logger: false,
});
fastify.register(memberRoutes);
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
Route prefix
Route prefix를 통해 특정 route에 문자열을 추가할 수 있다. 예를 들어 API server를 구축할 때 API를 version 별로 나누어 구분할 때 다음과 같이 설정할 수 있다.
import Fastify from "fastify";
import memberRoutesV1 from "./routes/v1/member.js";
import memberRoutesV2 from "./routes/v2/member.js";
const fastify = Fastify({
logger: false,
});
fastify.register(memberRoutesV1, {prefix:'/v1'});
fastify.register(memberRoutesV2, {prefix:'/v2'});
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
위의 예제에서 prefix가 /v1로 설정한 route의 url은 /v1/member가 되어 해당 route에 요청을 보내기 위해선 /v1/member url로 요청을 보내야 하고 /v2로 설정한 route의 url은 /v2/member가 되어 해당 route로 요청을 보내기 위해선 /v2/member url로 요청을 보내야 한다.
혹은 member route를 설정할 때 다음과 같이 url 부분을 모두 /로 설정해 놓고 prefix 부분에서 /member를 설정하여 일괄적으로 설정할 수도 있다.
/routes/member.js
const memberRoutes = (fastify, options) => {
fastify.get("/", opts, (request, reply) => {
reply.send({ hello: "world" });
});
fastify.post("/", async (request, reply) => {
const newMemberRequest = request.body;
const newMember = await createMember(newMemberRequest);
return reply.code(201).send(newMember);
});
fastify.patch("/:memberId", async (request, reply) => {
const { memberId } = request.params;
const updateMemberRequest = request.body;
const updatedMember = await updateMember(memberId, updateMemberRequest);
return reply.send(updatedMember);
});
};
export default memberRoutes;
app.js
import Fastify from "fastify";
import memberRoutes from "./routes/member.js";
const fastify = Fastify({
logger: false,
});
fastify.register(memberRoutes, {prefix:'/member'});
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
Typescript
Fastify에 대한 type definition은 package에 포함되어 있으므로 library에 대한 별도의 type package를 설치할 필요는 없다. 그 외에 project에 typescript를 적용하기 위해 필요한 package를 설치하자.
npm install -D typescript tsx
이제 다음 command를 실행하여 tsconfig.json을 생성해준다.
npx tsc --init
테스트를 위한 tsconfig 설정은 다음과 같다.
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["./src/**/*"]
}
tsx는 typescript를 compile하지 않고 바로 node 환경에서 실행할 수 있게 해주는 package다. pacakge를 설치했다면 다음과 같은 package.json에 script를 추가해준다. tsx에 watch option을 추가하면 file에 수정이 발생할 때 마다 node server를 새로 실행시켜준다.
...
"scripts": {
"start": "tsx watch ./src/app.ts",
"build": "tsc"
}
...
이제 기존의 app.js파일과 route/member.js를 모두 app.ts와 routes/member.ts로 변경해준다. 그리고 src folder를 생성하여 app.ts와 routes/member.ts를 모두 src folder로 이동 시킨다. 이동 시킨 후 project 구성은 다음과 같다.
tsconfig.json
package.json
package-lock.json
node_modules
src
- app.ts
/routes
- member.ts
이제 기존 파일에 기본적인 type을 적용해보자.
app.ts
import Fastify, { FastifyInstance } from "fastify";
import memberRoutes from "./routes/member.js";
const fastify: FastifyInstance = Fastify({
logger: false,
});
fastify.register(memberRoutes, { prefix: "/member" });
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
member.ts
...
import {
FastifyInstance,
FastifyPluginOptions,
FastifyReply,
FastifyRequest,
} from "fastify";
type MemberBody = {
Body: { address: string; name: string };
};
type MemberParams = {
Params: { memberId: string };
};
const memberRoutes = (
fastify: FastifyInstance,
options: FastifyPluginOptions
) => {
fastify.get("/member", (request: FastifyRequest, reply: FastifyReply) => {
reply.send({ hello: "world 11" });
});
fastify.post(
"/member",
async (request: FastifyRequest<MemberBody>, reply: FastifyReply) => {
const newMemberRequest = request.body;
const newMember = await createMember(newMemberRequest);
return reply.code(201).send(newMember);
}
);
fastify.patch(
"/member/:memberId",
async (request: FastifyRequest<MemberParams>, reply: FastifyReply) => {
const { memberId } = request.params;
const updateMemberRequest = request.body;
const updatedMember = await updateMember(memberId, updateMemberRequest);
return reply.send(updatedMember);
}
);
fastify.delete(
"/member/:memberId",
(request: FastifyRequest<MemberParams>, reply: FastifyReply) => {
const { memberId } = request.params;
await deleteMember(memberId);
reply.code(204).send();
}
);
};
export default memberRoutes;
npm run start를 통해 파일을 실행시켜 보면 compile 없이도 node server가 실행되는 것을 확인할 수 있다.
Route Options
Route를 추가할 때 특정 route에 적용될 수 있는 option을 설정할 수 있다. Route에 설정할 수 있는 option은 다양하지만 아래에선 그 중 일부를 살펴보며 모든 route option은 documentation을 참고하자. ( Reference - Fastify - Routes options )
method : router handler 처리할 request method를 설정한다.
GET, HEAD, TRACE, DELETE, OPTIONS, PATCH, PUT, POST를 설정할 수 있다. url이/member고 method가 get인 request는 아래 route에서 처리된다.import Fastify, { FastifyInstance } from "fastify"; const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", handler: (req, res) => { reply.send({ hello: "world" }); }, });url : route handler가 처리할 request의 url을 설정한다. url이
/member고 method가 post인 request는 아래 route에서 처리된다.import Fastify, { FastifyInstance } from "fastify"; const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "POST", url: "/member", handler: (req, res) => { ... }, });exposeHeadRoute : 현재 route handler의 method가 get일 때 자동으로 같은 route에 head method를 가진 route handler를 추가한다. ( default : true다 )
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", exposeHeadRoute: true, handler: (req, res) => { res.send({ hello: "world" }); }, });onRequest : request를 전달되었을 때 바로 실행될 function을 설정할 수 있다.
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", onRequest: (req, res, done) => { console.log(req.headers); done(); }, handler: (req, res) => { res.send({ hello: "world" }); }, });preHandler : request handler가 실행되기 바로 직전에 실행되는 function이다.
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", preHandler: (req, res, done) => { ... done(); }, handler: (req, res) => { res.send({ hello: "world" }); }, });onSend : response가 전달되기 바로 전에 실행되는 function이다.
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", handler: (req, res) => { res.send({ hello: "world" }); }, onSend: (req, res, payload, done) => { ... done(); }, });onResponse : response가 전달되고 나서 실행되는 function이다.
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", handler: (req, res) => { res.send({ hello: "world" }); }, onResponse: (req, res, done) => { ... done(); }, });handler : 해당 route handler로 들어오는 request를 처리하는 function이다.
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", handler: (req, res) => { res.send({ hello: "world" }); }, });onError : request 처리 중 error가 throw되면 실행되는 function이다.
const fastify: FastifyInstance = Fastify({ logger: false, }); fastify.route({ method: "GET", url: "/member", handler: (req, res) => { throw new Error("test error"); res.send({ hello: "world" }); }, onError: (req, res, error, done) => { ... done(); }, });bodyLimit : route handler가 허용하는 최대 body 사이즈를 bytes 크기로 설정한다. defautl는 1mb로 설정된다.
![[ 살펴보기 ] RDB - Relationships](https://cdn.hashnode.com/res/hashnode/image/upload/v1739711556668/48dc9e84-a621-42aa-9c9f-5fc5c436f0ec.jpeg)
![[ 살펴보기 ] MySQL - Data types](https://cdn.hashnode.com/res/hashnode/image/upload/v1739593589113/530f8704-4d27-42c9-a451-bb5c63150b99.jpeg)
![[ 살펴보기 ] TypeORM - Transactions, Migration](https://cdn.hashnode.com/res/hashnode/image/upload/v1739106042581/980b8133-61d4-406a-a026-65be9c28eace.jpeg)
![[ 살펴보기 ] TypeORM - Relations](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666874402/b688bd0b-b6bb-4f43-87d8-c1b46b59f1b7.jpeg)
![[ 살펴보기 ] TypeORM - Basics](https://cdn.hashnode.com/res/hashnode/image/upload/v1738666803591/bef5df17-7dc7-4123-ae55-004d5042df39.jpeg)