[ 살펴보기 ] Fastify - Typesciprt, Type Provider
![[ 살펴보기 ] Fastify - Typesciprt, Type Provider](https://cdn.hashnode.com/res/hashnode/image/upload/v1730261716774/bccebabe-1c12-43c1-a09e-c6affc41450d.jpeg)
Typesciprt 기반으로 fastify를 사용한다면 fastify와 더불어 typescirpt 환경에 필요한 추가 package를 설치한다.
npm install -D typescript @types/node
추가로 tsx package는 typescirpt file을 compile 과정없이 바로 실행할 수 있게 해주는 package다. 해당 package를 통해 development 환경에서 compile없이 typescirpt file을 실행하기 위해 추가로 설치한다.
npm install -D tsx
그리고 package.json script에 다음과 같이 추가해 준다.
...
"scripts": {
"dev": "tsx watch ./src/app.ts",
"build": "tsc",
"start": "node ./dist/app.js",
}
...
dev : tsx를 통해 typescirpt file을 compile없이 node환경에서 실행하기 위한 script
build : typescirpt compiler를 통해 typescirpt file을 javasciprt file로 compile한다. Project folder에 tsconfig.json 파일이 있으면 tsconfig에 설정된 값에 따라 compile을 실행한다.
start : build command를 통해 compile된 javascript file을 node를 통해 실행한다. tsconfig를 통해 compile된 file을 dist folder에 담도록 설정을 할 것이므로
./dist/app.js경로로 설정한다.
그리고 typescript 설정을 위해 project root 경로에 ( project package.json파일이 존재하는 위치 ) tsconfig.json을 생성한다. 아래의 포스트는 다음과 같은 설정으로 진행한다.
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["./src/**/*"]
}
테스트를 위한 project structure는 다음과 같다.
package.json
tsconfig.json
src
app.ts
/routes
- member.ts
우선 app.ts 파일에 다음과 같이 기본 server 구성을 추가해보자.
import Fastify from "fastify";
const fastify = Fastify({
logger: true,
});
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
그리고 /routes 폴더에 member.ts 파일을 생성해 member route를 구성한다.
특정 route가 client request에 포함된 queryString, header를 기반으로 request를 처리한다면 queryString과 header 또한 type을 지정해 route에 적용할 수 있다.
...
type MemberRouteGet = {
Querystring: { username: string };
Headers: { authorization: string };
};
fastify.get<MemberRouteGet>("/", (request, reply) => {
console.log(" ::: member get ::: ");
const { username } = request.query;
const { authorization } = request.headers;
reply.send({ hello: "world 22" });
});
...
여기에 더불어 response status code에 따라 response structure type을 강제할 수도 있다. 아래의 예제는 status code가 200일 때는 { success:boolean }의 response type을 적용하고 status code가 400번대일 때는 { error:boolean } response type을 적용한다.
type MemberRouteGet = {
Querystring: { username: string };
Headers: { authorization: string };
Reply: {
200: { success: boolean };
"4xx": { error: boolean };
};
};
fastify.get<MemberRouteGet>("/", (request, reply) => {
console.log(" ::: member get ::: ");
const { username } = request.query;
const { authorization } = request.headers;
if (authorization) {
reply.code(200).send({ success: true });
return;
}
reply.code(400).send({ error: true });
});
위의 예제를 기반으로 구성한 간단한 member route의 구성은 다음과 같다.
import { FastifyInstance, FastifyPluginOptions } from "fastify";
type MemberRouteResponse = {
Reply: {
200: { success: boolean };
"4xx": { error: boolean };
};
};
type MemberRouteHeader = {
Headers: { authorization: string };
};
type MemberRouteBody = {
Body: { address: string; name: string };
};
type MemberRouteParam = {
Params: { memberid: string };
};
type MemberRouteGet = {
Querystring: { username: string };
} & MemberRouteHeader &
MemberRouteResponse;
type MemberRoutePost = MemberRouteHeader &
MemberRouteBody &
MemberRouteResponse;
type MemberRoutePatch = MemberRouteHeader &
MemberRouteParam &
MemberRouteBody &
MemberRouteResponse;
type MEmberRouteDelete = MemberRouteHeader &
MemberRouteParam &
MemberRouteResponse;
const memberRoutes = (
fastify: FastifyInstance,
options: FastifyPluginOptions
) => {
fastify.get<MemberRouteGet>("/", (request, reply) => {
const { username } = request.query;
const { authorization } = request.headers;
if (authorization) {
reply.code(200).send({ success: true });
return;
}
reply.code(400).send({ error: true });
});
fastify.post<MemberRoutePost>("/", (request, reply) => {
const { address, name } = request.body;
const { authorization } = request.headers;
if (authorization) {
reply.code(200).send({ success: true });
return;
}
reply.code(400).send({ error: true });
});
fastify.patch<MemberRoutePatch>("/", (request, reply) => {
const { memberid } = request.params;
const { address, name } = request.body;
const { authorization } = request.headers;
if (authorization) {
reply.code(200).send({ success: true });
return;
}
reply.code(400).send({ error: true });
});
fastify.delete<MEmberRouteDelete>("/:memberId", (request, reply) => {
const { memberid } = request.params;
const { authorization } = request.headers;
if (authorization) {
reply.code(200).send({ success: true });
return;
}
reply.code(400).send({ error: true });
});
};
export default memberRoutes;
이제 해당 router를 fastify에 등록해 사용해보자. 아래 예제처럼 route를 등록하고 npm run dev command를 통해 server를 실행하면 member route가 정상적으로 동작하는 것을 확인할 수 있다.
import Fastify from "fastify";
import memberRoutes from "./routes/member.js";
const fastify = Fastify({
logger: true,
});
fastify.register(memberRoutes, { prefix: "/member" });
const serverStart = async () => {
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
serverStart();
Type Provider
Type provider를 사용하면 route option에 추가되는 schema 객체를 기반으로 type을 추론해 사용하거나 schema json 파일을 기반으로 type을 생성할 수도 있다. Fastify에서 지원하는 type provider중 선택지는 몇 가지 있지만 해당 포스트에선 typebox type provider를 통해 schema 객체를 기반으로 type을 추론하여 적용하는 예제를 살펴본다. ( Reference - Type-Providers )
먼저 typebox type provider를 적용하기 위해선 다음 package가 필요하다.
npm i -D @fastify/type-provider-typebox @sinclair/typebox
예제를 간단히 하기 위해 post route만 별도로 분리하여 schema 객체를 적용하고 schema를 통해 추론한 type이 적용되는 살펴보자.
app.ts
...
import { TypeBoxTypeProvider } from "@fastify/type-provider-typebox";
const fastify = Fastify({
logger: true,
}).withTypeProvider<TypeBoxTypeProvider>();
export type MyFastifyInstance = typeof fastify;
...
member.ts
import { Type } from "@sinclair/typebox";
import { MyFastifyInstance } from "../app.js";
const memberRoutePostSchema = {
body: Type.Object({
address: Type.String(),
name: Type.Optional(Type.String()),
}),
headers: Type.Object({
authorization: Type.String(),
}),
};
const memberRoutes = (
fastify: MyFastifyInstance,
options: FastifyPluginOptions
) => {
fastify.post(
"/",
{
schema: memberRoutePostSchema,
},
(request, reply) => {
const { address, name } = request.body;
const { authorization } = request.headers;
if (authorization) {
reply.code(200).send({ success: true });
return;
}
reply.code(400).send({ error: true });
}
);
...
};
위의 코드에서 확인할 수 있듯이 post route에 별도의 타입을 generic type으로 넘겨주지 않아도 router schema option에 적용된 memberRoutePostSchema를 기반으로 route hnadler에서 request body와 header의 타입을 추론해준다.
![[ 살펴보기 ] 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)