Next.js API 경로 - 전역 오류 처리 및 깨끗한 코드 관행
파일 시스템 기반 라우팅을 사용하고 API 경로를 생성하는 유연한 방법도 제공합니다. 이는 Next.js에서 간단하게 서버리스 기능을 생성하는 좋은 방법입니다.
그러나 단순성과 유연성으로 인해 다음과 같은 문제가 발생합니다.
그래서 이러한 문제를 해결하기 위해 모든 중복 코드와 오류 처리를 추상화하여 핵심 비즈니스 논리에 집중할 수 있도록 하는 고차 함수(HOF)를 고안했습니다. 걱정할 필요가 있는 것은 언제 응답을 반환하고 언제 오류를 발생시켜야 하는지입니다. 오류 응답은 전역 오류 처리기(중앙 오류 처리)에서 자동으로 생성됩니다.
크레딧: 이 방법은 https://jasonwatmore.com/post/2021/08/23/next-js-api-global-error-handler-example-tutorial에서 영감을 받았습니다.
자, 고민하지 말고 들어가 봅시다.
챕터
참고: 이것은 DEV에 대한 나의 첫 번째 게시물이므로 제안을 환영하는 것이 아니라 필수입니다! :피
1. 프로젝트 설정
Start by creating a Next.js project using the create-next-app
npm script. Notice that I'm using the --ts
flag to initialize with TypeScript because it's 2022 folks, make the switch!
npx create-next-app@latest --ts
# or
yarn create next-app --ts
We'll need to install two additional packages; one for schema validation (I prefer yup
) and another for declaratively throwing errors, http-errors
.
npm i yup http-errors && npm i --dev @types/http-errors
# or
yarn add yup http-errors && yarn add --dev @types/http-errors
2. API 핸들러 고차 함수
Create a new file ./utils/api.ts
. This file exports the apiHandler()
function which acts as the entry point for any API route. It accepts a JSON mapping of common HTTP request methods and methodHandler()
functions. It returns an async function that wraps all the API logic into a try-catch
block which passes all the errors to errorHandler()
. More on that later.
// ./utils/api.ts
import createHttpError from "http-errors";
import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";
import { Method } from "axios";
// Shape of the response when an error is thrown
interface ErrorResponse {
error: {
message: string;
err?: any; // Sent for unhandled errors reulting in 500
};
status?: number; // Sent for unhandled errors reulting in 500
}
type ApiMethodHandlers = {
[key in Uppercase<Method>]?: NextApiHandler;
};
export function apiHandler(handler: ApiMethodHandlers) {
return async (req: NextApiRequest, res: NextApiResponse<ErrorResponse>) => {
try {
const method = req.method
? (req.method.toUpperCase() as keyof ApiMethodHandlers)
: undefined;
// check if handler supports current HTTP method
if (!method)
throw new createHttpError.MethodNotAllowed(
`No method specified on path ${req.url}!`
);
const methodHandler = handler[method];
if (!methodHandler)
throw new createHttpError.MethodNotAllowed(
`Method ${req.method} Not Allowed on path ${req.url}!`
);
// call method handler
await methodHandler(req, res);
} catch (err) {
// global error handler
errorHandler(err, res);
}
};
}
request.method
, we throw a 405 (Method Not Allowed) Error . http-errors
를 사용하여 어떻게 오류가 발생하는지 확인하세요. 이것이 비즈니스 로직에서 예상되는 오류를 처리하는 방법입니다. 일관된 오류 응답을 생성하는 프로젝트에서 모든 개발자가 따라야 하는 규칙을 시행합니다.참고: 내부
ApiMethodHandlers
에서 Method
의 axios
유형을 사용하고 있습니다. axios
를 설치하지 않으려면 다음과 같이 정의할 수 있습니다.export type Method =
|'GET'
|'DELETE'
|'HEAD'
|'OPTIONS'
|'POST'
|'PUT'
|'PATCH'
|'PURGE'
|'LINK'
|'UNLINK';
3. 전역 오류 처리기
When an error is thrown anywhere in our APIs, it'll be caught by the top level try-catch
block defined in our apiHandler()
(unless ofcourse we define another error boundary somewhere deeper). The errorHandler()
checks for the type of error and responds accordingly.
// ./utils/api.ts
import { ValidationError } from "yup";
function errorHandler(err: unknown, res: NextApiResponse<ErrorResponse>) {
// Errors with statusCode >= 500 are should not be exposed
if (createHttpError.isHttpError(err) && err.expose) {
// Handle all errors thrown by http-errors module
return res.status(err.statusCode).json({ error: { message: err.message } });
} else if (err instanceof ValidationError) {
// Handle yup validation errors
return res.status(400).json({ error: { message: err.errors.join(", ") } });
} else {
// default to 500 server error
console.error(err);
return res.status(500).json({
error: { message: "Internal Server Error", err: err },
status: createHttpError.isHttpError(err) ? err.statusCode : 500,
});
}
}
All unforeseen errors are considered unhandled and are presented as 500 Internal Server Error
s to the users. I also chose to send the stack trace and log it in this case for debugging.
4. 예제 API 경로 생성
With that, ourapiHanlder()
is complete and can now be used instead of a plain old function export 모든 API 경로 파일 내부.실제 작동을 확인하기 위해 데모 경로를 만들어 봅시다.
가짜 블로그 REST api
/api/article?{id: string}
를 만들어서 보여드리겠습니다. ./pages/api/
아래에 파일을 만들고 이름을 article.ts
로 지정합니다.// ./pages/api/article.ts
import createHttpError from "http-errors";
import * as Yup from "yup";
import { NextApiHandler } from "next";
import { apiHandler } from "utils/api";
import { validateRequest } from "utils/yup";
// Fake DB to demonstrate the API
const BLOG_DB = [
{
id: 1,
title: "Top 10 anime betrayals",
content: "Lorem ipsum dolor sit amet ....",
publishedTimestamp: 1665821111000,
},
];
type GetResponse = {
data: typeof BLOG_DB | typeof BLOG_DB[number];
};
/**
* returns all articles or the article with the given id if query string is provided
*/
const getArticle: NextApiHandler<GetResponse> = async (req, res) => {
const { id } = req.query;
if (id) {
// find and return article with given id
const article = BLOG_DB.find((article) => article.id === Number(id));
if (!article)
throw new createHttpError.NotFound(`Article with id ${id} not found!`);
// OR
// if (!article) throw new createHttpError[404](`Article with id ${id} not found!`)
res.status(200).json({ data: article });
} else {
res.status(200).json({ data: BLOG_DB });
}
};
type PostResponse = {
data: typeof BLOG_DB[number];
message: string;
};
const postArticleSchema = Yup.object().shape({
title: Yup.string().required("Title is required!"),
content: Yup.string()
.required("Content is required!")
.max(
5000,
({ max }) => `Character limit exceeded! Max ${max} characters allowed!`
),
publishedTimestamp: Yup.number()
.required("Published timestamp is required!")
.lessThan(Date.now(), "Published timestamp cannot be in the future!"),
});
const createArticle: NextApiHandler<PostResponse> = async (req, res) => {
const data = validateRequest(req.body, postArticleSchema);
const newArticle = { ...data, id: BLOG_DB.length + 1 };
BLOG_DB.push(newArticle);
res
.status(201)
.json({ data: newArticle, message: "Article created successfully!" });
};
type DeleteResponse = {
data: typeof BLOG_DB[number];
message: string;
};
const deleteArticleById: NextApiHandler<DeleteResponse> = async (req, res) => {
const { id } = req.query;
if (!id) throw new createHttpError.BadRequest("Article id is required!");
const articleIndex = BLOG_DB.findIndex(
(article) => article.id === Number(id)
);
if (articleIndex < 0)
throw new createHttpError.NotFound(`Article with id ${id} not found!`);
BLOG_DB.splice(articleIndex, 1);
res.status(200).json({
data: BLOG_DB[articleIndex],
message: "Article deleted successfully!",
});
};
export default apiHandler({
GET: getArticle,
POST: createArticle,
DELETE: deleteArticleById,
});
validateRequest()
함수는 yup 스키마를 가져와 JSON의 유효성을 검사하는 도우미입니다. 또한 적절한 유형의 검증된 데이터를 반환합니다. 이 검증이 실패하면 errorHandler()
에서 처리하는 ValidationError가 발생합니다.// ./utils/yup.ts
import { ObjectSchema } from "yup";
import { ObjectShape } from "yup/lib/object";
export function validateRequest<T extends ObjectShape>(
data: unknown,
schema: ObjectSchema<T>
) {
const _data = schema.validateSync(data, {
abortEarly: false,
strict: true,
});
return _data;
}
Postman 또는 원하는 다른 도구를 사용하여 방금 생성한 API를 테스트할 수 있습니다.
5. 마무리
Notice how clean and descriptive our business logic looks ✨
✅ res.json()
is called only when we need to send a success response.
✅ In all other cases we throw the appropriate error contructed by http-errors
and leave the rest on the parent function.
✅ Controllers are divided into functions and plugged into the API Route by their respective req.method
which is kind of reminiscent of how Express.js routes are defined.
Enforcing clean code practices in our projects increases code readability which matters a lot as the scale of the project starts increasing. 💪🏽
I don't claim that this is the best way to achieve this goal so if you know of any other better ways, do share! 🙌🏽
I hope you liked this post and it'll help you in your Next.js projects. If it helped you with a project, I'd love to read about it in the comments. 💜
Shameless Plug
Checkout my recently published Modal library for React.js :3
6. 소스 코드
은밀한 선생님 / 다음-API-핸들러-예제
Dev.to의 내 게시물에 대한 동반 예제 프로젝트
일관성과 깨끗한 코드를 위해 apiHandler 고차 함수를 사용하여 API 경로 생성 🔥
특징 ⚡
res.json()
는 성공 응답을 보내야 하는 경우에만 호출됩니다. req.method
에 연결됩니다. 이는 Express.js 경로가 정의되는 방식을 연상시킵니다. 기술 🧪
설치 📦
First, run the development server: ```bash npm run dev # or yarn dev
사용해 보세요! 🚀
Dev.to 📖에서 관련 게시물을 확인하세요.
View on GitHub
Reference
이 문제에 관하여(Next.js API 경로 - 전역 오류 처리 및 깨끗한 코드 관행), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/sneakysensei/nextjs-api-routes-global-error-handling-and-clean-code-practices-3g9p텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)