[ 살펴보기 ] Fastify - Validation, Serialization
![[ 살펴보기 ] Fastify - Validation, Serialization](https://cdn.hashnode.com/res/hashnode/image/upload/v1730078610663/e131ca69-91a8-4ab3-86b2-3d23518ad185.jpeg)
Fastify는 router option중 schema를 통해 reqeust가 router handler에 도달하기 전에 request의 body나 querystring에 필요한 값이 전달되었는지 체크할 수 있다.
Validation
Post method를 처리하는 route는 보통 reqeust body에 포함된 정보를 기반으로 새로운 resource를 생성한다. 이 때 request body에 포함되어 전달되는 데이터 중 필수로 전달되어야 하는 데이터를 schema를 통해 지정할 수 있다.
import Fastify, { FastifyInstance, RouteShorthandOptions } from "fastify";
const fastify: FastifyInstance = Fastify({
logger: false,
});
const postSchema: RouteShorthandOptions["schema"] = {
body: {
type: "object",
required: ["address"],
properties: {
address: { type: "string" },
},
},
};
fastify.route({
method: "POST",
url: "/member",
schema: postSchema,
handler: (req, res) => {
res.send({ hello: "post world" });
},
});
위의 예제에선 request body에 대한 schema를 설정하여 post route의 option으로 전달하고 있다. 위의 예제에서 볼 수 있듯이 request body에 필수로 포함되어야 할 값은 required array에 추가해준다.
이제 위의 post method router에 address property를 body에 포함하지 않은 request를 보내면 client는 다음과 같은 error response를 전달받는다.
{
"statusCode": 400,
"code": "FST_ERR_VALIDATION",
"error": "Bad Request",
"message": "body must have required property 'address'"
}
Request body뿐만 아니라 request의 queryString이나 header 역시 schema를 설정해 적용할 수 있다. 다음은 request에 전달되는 queryString을 검사하는 schema를 적용하는 예제다.
import Fastify, { FastifyInstance, RouteShorthandOptions } from "fastify";
const fastify: FastifyInstance = Fastify({
logger: false,
});
const getSchema: RouteShorthandOptions["schema"] = {
querystring: {
type: "object",
required: ["name"],
properties: {
name: { type: "string" },
},
},
};
fastify.route({
method: "GET",
url: "/member",
schema: getSchema,
handler: (req, res) => {
res.send({ hello: "get world" });
},
});
이번엔 body property가 아닌 queryString property에 schema를 적용해 request url에 포함된 queryStirng을 검사한다. 만약 위의 route로 전달되는 request에 name queryString이 포함되지 않으면 client에게 다음과 같은 에러 resposne가 전달된다.
"statusCode": 400,
"code": "FST_ERR_VALIDATION",
"error": "Bad Request",
"message": "querystring must have required property 'name'"
}
Request의 body나 queryString이 아닌 특정 header를 포함하고 있는지 역시 schema를 통해 체크할 수 있다. 다음은 authorization header가 포함되어 있는지 체크 하는 schema를 적용한 route의 예제다.
import Fastify, { FastifyInstance, RouteShorthandOptions } from "fastify";
const fastify: FastifyInstance = Fastify({
logger: false,
});
const getSchema: RouteShorthandOptions["schema"] = {
headers: {
type: "object",
required: ["authorization"],
properties: {
authorization: { type: "string" },
},
},
};
fastify.route({
method: "GET",
url: "/member",
schema: getSchema,
handler: (req, res) => {
res.send({ hello: "get world" });
},
});
Serialization
Fastify schema를 통해 특정 status code response에 대한 data property를 설정하면 schema에 설정된 property만 response data에 포함된다. 다음 예제를 살펴보자.
const postSchema: RouteShorthandOptions["schema"] = {
response: {
201: {
type: "object",
properties: {
name: { type: "string" },
address: { type: "string" },
},
},
},
};
fastify.route({
method: "POST",
url: "/member",
schema: postSchema,
handler: (req, res) => {
res.status(201).send(req.body);
},
});
위의 예제에선 201 status code response에 대한 schema를 설정하여 route handler의 option으로 전달하고 있다. 그리고 route handler는 request에 포함된 body를 그대로 response를 통해 전달하게 구성되어 있다.
만약 위의 예제를 기준으로 아래와 같이 총 세 개의 body data를 포함해서 request를 보내더라도 response로 돌아오는 data는 name과 address뿐이다. 201 response schema에 선언된 properties가 name와 address이기 때문에 fastify에서 response를 전달할 때 schema에 선언된 properties가 아니면 자동으로 제외시킨다.
{
"name":"jake",
"address":"224 morris",
"city":"new york"
}
만약 200번대 status code에 동일한 schema를 적용하고자 한다면 다음과 같이 2xx 형식으로 지정해줄 수 있다.
const postSchema: RouteShorthandOptions["schema"] = {
response: {
"2xx": {
type: "object",
properties: {
name: { type: "string" },
address: { type: "string" },
},
},
},
};
fastify.route({
method: "POST",
url: "/member",
schema: postSchema,
handler: (req, res) => {
res.status(201).send(req.body);
},
});
위의 코드는 200, 201, 202등 200번대 status code response에 동일한 schema를 적용한다. 또는 다음과 같이 200번대 status code를 위한 schema와 default response schema를 함께 설정해 default response schema를 error에 대한 response schema로 사용할 수도 있다.
const postSchema: RouteShorthandOptions["schema"] = {
response: {
default: {
type: "object",
properties: {
statusCode: { type: "number" },
message: { type: "string" },
},
},
"2xx": {
type: "object",
properties: {
name: { type: "string" },
address: { type: "string" },
},
},
},
};
fastify.route({
method: "POST",
url: "/member",
schema: postSchema,
handler: (req, res) => {
res.status(400).send({ message: "test error", statusCode: 400 });
},
});
위의 schema를 적용한 route handler에서 400, 500번대 status code response에는 default response schema에서 설정한 statusCode, message properties만 포함되고 다른 properties는 제외된다.
Error Handling
만약 default로 제공되는 error response가 아닌 error가 발생했을 때 response를 직접 정의하고 싶다면 route handler의 errorHandler를 통해서 error response를 직접 정의할 수 있다.
const postSchema: RouteShorthandOptions["schema"] = {
body: {
type: "object",
required: ["address"],
properties: {
address: { type: "string" },
},
},
};
fastify.route({
method: "POST",
url: "/member",
schema: postSchema,
handler: (req, res) => {
res.send({ hello: "post world" });
},
errorHandler: (err, req, reply) => {
const { code, message, statusCode } = err;
let response: ErrorResponse;
const responseStatus = statusCode ?? 400;
if (code === "FST_ERR_VALIDATION") {
response = {
message: "Not valid body",
code,
};
reply.status(responseStatus).send({ code, message: "Not valid body" });
return;
}
reply.status(responseStatus).send({ message, code });
},
});
위의 예제는 errorHandler를 통해 error code가 “FST_ERR_VALIDATION”일 때 response message를 default message가 아닌 custom message로 수정해 전달하는 예제다.
또는 request validation이 실패했을 때 error 처리를 route handler안에서 직접 처리하고 싶다면 route option중 attachValidationoption을 true로 설정해준다. attachValidationoption을 true로 설정해주면 route handler의 request object에서 validationError property를 통해 reqeust validation error 정보를 확인할 수 있다.
const postSchema: RouteShorthandOptions["schema"] = {
body: {
type: "object",
properties: {
name: { type: "string" },
address: { type: "string" },
},
required: ["name"],
}
};
fastify.route({
method: "POST",
url: "/member",
attachValidation: true,
schema: postSchema,
handler: (req, res) => {
if (req.validationError) {
res.status(400).send(req.validationError);
}
res.status(201).send(req.body);
},
});
![[ 살펴보기 ] 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)