Next.js에서 API 만들기
Next.js에서 pages/api 디렉토리에서 파일/디렉토리 이름으로 API endpoint를 나타낼 수 있다.
pages/api/user.ts
export default function handler(req, res) {
if (req.method === 'GET') {
// GET 요청 처리
} else if (req.method === 'POST') {
// POST 요청 처리
}
}
/user로 들어온 요청이 pages/api/user.ts에서 처리된다.
next-connect 라이브러리를 사용하면 마치 Express.js에서처럼 미들웨어의 형태로 코드를 작성할 수 있다.
import { NextApiRequest, NextApiResponse } from 'next';
import nextConnect from 'next-connect';
const handler = nextConnect<NextApiRequest, NextApiResponse>();
handler.get((req, res) => {
// GET 요청 처리
});
handler.post((req, res) => {
// POST 요청 처리
});
export default handler;
실제 프로젝트에서 사용한 방법
models/User.ts
import mongoose, { Schema } from 'mongoose';
export const UserSchema = new Schema(
{
userId: {
type: String,
unique: true,
required: true,
match: /[a-z0-9_]{4,16}/
},
userName: {
type: String,
required: true,
match: /^[가-힣]{2,8}$/
},
nickname: {
type: String,
unique: true,
required: true,
match: /[a-zA-Z0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,8}/
},
phone: {
type: String,
required: true,
match: /^\d{3}-\d{3,4}-\d{4}$/
},
email: {
type: String,
unique: true,
required: true,
match:
/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/
},
money: {
type: Number,
default: 1000,
min: 0,
max: Number.MAX_SAFE_INTEGER
}
},
{ timestamps: true }
);
export default mongoose.models.User || mongoose.model('User', UserSchema);
lib/mongoose/dbConnect.ts
import mongoose from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';
const { MONGODB_URI } = process.env;
export default async (
req: NextApiRequest,
res: NextApiResponse,
next: Function
) => {
if (!global.mongoose) {
global.mongoose = await mongoose.connect(MONGODB_URI);
}
return next();
};
global에 mongoose를 캐시해서 mongoose.connect를 한 번만 실행하도록 한다.
lib/mongoose/createHandler.ts
import { NextApiRequest, NextApiResponse } from 'next';
import nextConnect, { Middleware } from 'next-connect';
import dbConnect from './dbConnect';
export default (
...middleware: Middleware<NextApiRequest, NextApiResponse>[]
) => {
return nextConnect<NextApiRequest, NextApiResponse>({
onError: (err, req, res) => {
// 에러 발생시 처리 내용
},
onNoMatch: (req, res) => {
// 어떠한 route에도 매치되지 않았을 때 처리 내용
}
}).use(dbConnect, ...middleware);
};
위에서 작성한 dbConnect 미들웨어를 포함하고, 인자로 다른 미들웨어를 받아서 사용할 수 있는 핸들러를 반환한다.
onError에는 에러 발생시 처리할 내용을, onNoMatch에는 아무런 route에도 매치되지 않았을 때 (404 error) 처리할 내용을 작성할 수 있다.
pages/api/user/index.ts
import mongoose from 'mongoose';
import createHandler from 'lib/mongoose/createHandler';
const handler = createHandler();
handler.get(async (req, res) => {
// GET 요청 처리 내용
});
handler.post(async (req, res) => {
// POST 요청 처리 내용
});
export default handler;
mongoose의 populate를 사용한 타 collection 참조
mongoose의 populate를 사용하여 ObjectId를 컬렉션 객체로 치환할 수 있다.
위에 User 스키마에 유저가 작성한 리뷰들을 모아놓은 reviews라는 field가 있고, 이 reviews라는 field에 Review collection의 ObjectId를 저장한다.
models/User.ts
export const UserSchema = new Schema(
{
...
reviews: {
type: [Schema.Types.ObjectId],
ref: 'Review'
}
...
}
);
models/Review.ts
export const ReviewSchema = new Schema(
{
...
rating: {
type: Number,
required: true,
min: 1,
max: 5
},
content: {
type: String,
required: true,
minlength: 10,
maxlength: 1000
},
...
}
);
pages/api/user/index.ts
...
import User from 'models/User';
handler.get(async (req, res) => {
const data = await User.find({}).populate('reviews');
res.json(data);
});
...
const { data } = axios.get('/api/user');
console.log(data);
// {
// userId: 'gildong123',
// userName: '홍길동',
// nickName: '길동',
// phone: '010-1234-5678',
// email: 'gildong123@naver.com',
// money: 100000,
// reviews: [
// {
// rating: 4,
// content: '배송이 빨라서 좋아요~'
// },
// {
// rating: 5,
// content: '향이 너무 좋아요!!'
// },
// {
// rating: 1,
// content: '포장이 뜯어져서 왔어요 ㅡㅡ'
// }
// ]
// }
만약 populate를 사용하지 않았다면 reviews에는 ObjectId만 담겨 있었을 것이다.
그런데 Next.js에서 mongoose를 사용할 때 populate를 하게 되면 MissingSchemaError가 발생한다.
pages/api/user/index.ts에서는 User 모델만 import 하고 있는데, populate를 하는 시점에서 단 한 번도 Review 모델이 import 되지 않았다면 이러한 현상이 발생한다.
이 문제를 해결하기 위해서 처음에 DB를 연결할 때 모든 모델들을 같이 생성하기로 하였다.
lib/mongoose/dbConnect.ts
import mongoose from 'mongoose';
import { NextApiRequest, NextApiResponse } from 'next';
import { UserSchema } from 'models/User';
import { ReviewSchema } from 'models/Review';
const { MONGODB_URI } = process.env;
export default async (
req: NextApiRequest,
res: NextApiResponse,
next: Function
) => {
if (!global.mongoose) {
global.mongoose = await mongoose.connect(MONGODB_URI);
mongoose.model('User', UserSchema);
mongoose.model('Review', ReviewSchema);
}
return next();
};
참고자료
https://github.com/hoangvvo/next-connect
https://itnext.io/using-mongoose-with-next-js-11-b2a08ff2dd3c
https://nextjs.org/docs/api-routes/introduction
https://velog.io/@familyman80/%EB%AA%BD%EA%B3%A0DB-%EB%A1%9C%EC%BB%AC-ATLAS-%EA%B2%BD%EB%A1%9C
'IT > Next.js' 카테고리의 다른 글
[Next.js] React Query로 SSR 구현하기 (1) | 2022.11.18 |
---|---|
[Next.js] Next.js에서 Prop `className` did not match 경고가 뜨는 이유 (1) | 2022.03.05 |