app/api/hello.ts에 위와같은 코드를 작성후 http://localhost:3000/api 엔드포인트로 접속을 합니다.
서버리스 함수를 이용한 API 라우팅
위와 같이 아주 간단하게 구현할 수 있습니다.
next.js에서 API route는 클라이언트 측(javascript)가 아닌 서버에서 실행되는 함수(컴포넌트) 이며, app/api/ 폴더 내의 파일들은 자동으로 서버에서 실행이 가능한 API 엔드포인트가 됩니다.
API 라우트에서는 GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS 메서드를 사용할 수 있으며, NextRequest와 NextResponse 객체를 사용하여 HTTP 요청을 처리하고 응답할 수 있습니다.
또한, API 라우트에서 데이터베이스와 연결하고, 해당 모듈을 API 라우트에 가져와서 사용하는 것도 가능합니다.
다음은 실제 프로젝트에서 MongoDBAtlas와 연결하여 사용한 예시를 보여드리겠습니다.
🔍️ API route에서 DB 연결하기
// Mongoose 라이브러리를 사용하여 MongoDB 데이터베이스에 연결하는 모듈
import type _mongoose from "mongoose";
import { connect } from "mongoose";
// 전역 네임스페이스에 mongoose 객체를 포함
declare global {
var mongoose: {
promise: ReturnType<typeof connect> | null;
conn: typeof _mongoose | null;
};
}
// 환경 변수에서 MongoDB URI를 가져오기
const { NEXT_PUBLIC_MONGODB_URI } = process.env;
// MongoDB URI가 정의되어 있지 않으면 에러 처리
if (!NEXT_PUBLIC_MONGODB_URI) {
throw new Error(
"Please define the MONGODB_URI environment variable inside .env.local"
);
}
// 전역 네임스페이스에 캐시된 mongoose 객체가 있는지 확인
let cached = global.mongoose;
// 캐시된 mongoose 객체가 없다면 전역 네임스페이스에 초기화
if (!cached) {
global.mongoose = { conn: null, promise: null };
cached = { conn: null, promise: null };
}
// MongoDB 데이터베이스에 연결하기 위한 함수
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
// 연결 중인 상태가 아니라면 새로운 연결을 시작
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
// 연결을 시작하고 프로미스를 저장
cached.promise = connect(NEXT_PUBLIC_MONGODB_URI!, opts).then(
(mongoose) => {
return mongoose;
}
);
}
try {
// 연결 프로미스가 해결되기를 기다리고 연결을 저장
cached.conn = await cached.promise;
} catch (e) {
// 연결 중 오류가 발생하면 프로미스를 재설정하고 오류 발생
cached.promise = null;
throw e;
}
// MongoDB 연결을 반환
return cached.conn;
}
export default dbConnect;
이 함수 dbConnect는 MongoDB 데이터베이스에 대한 연결을 설정하는 역할을 합니다.
먼저 캐시된 연결이 있는지 확인하고 있다면 반환하고, 그렇지 않으면 Mongoose의 connect 함수를 사용하여 새로운 연결을 시작합니다.
// DB를 연결하고, HTTP POST 요청을 처리하는 함수
import { NextRequest, NextResponse } from "next/server";
import dbConnect from "../db-connect";
import Data from "./model";
const BAD_REQUEST = 400;
const INTERNAL_SERVER_ERROR = 500;
//요청 데이터의 유효성을 검사하는 함수
function isValidData(data: any): boolean {
return (
data &&
typeof data.name === "string" &&
typeof data.contact === "string" &&
typeof data.company === "string" &&
typeof data.item === "string" &&
typeof data.address === "string" &&
typeof data.agreedToTerms === "boolean"
);
}
export async function POST(request: Request) {
await dbConnect();
try {
const body = await request.json();
// 요청 데이터의 유효성을 검사
if (!isValidData(body)) {
return NextResponse.json(
{ error: "Bad Request - Invalid Data" },
{ status: BAD_REQUEST }
);
}
const createdData = await Data.create(body);
return NextResponse.json(createdData, { status: 201 });
} catch (error) {
console.error("Error creating example:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: INTERNAL_SERVER_ERROR }
);
}
}
dbConnect() 함수를 실행하여, MongoDBAtlas 와 연결하고 POST 요청을 처리합니다.
클라이언트에서 받은 요청데이터의 유효성검사를 한 뒤, const createdData = await Data.create(body); 부분을 통해 요청 데이터를 실제 DB에 저장하게 됩니다.
그러면 여기서 import한 Data의 model.ts 파일을 살펴보도록 하겠습니다.
import type { Model } from "mongoose";
import mongoose, { model } from "mongoose";
interface IData {
name: string;
contact: string;
company: string;
item: string;
address: string;
addressDetail: string;
agreedToTerms: Boolean;
isDone: Boolean;
date: Date;
}
const DataSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Please provide a name"],
maxlength: [10, "name cannot be more than 10 characters"],
},
contact: {
type: String,
required: [true, "Please provide a contact"],
},
company: {
type: String,
required: [true, "Please provide a company"],
},
item: {
type: String,
required: [true, "Please provide a item"],
},
address: {
type: String,
required: [true, "Please provide a address"],
},
addressDetail: {
type: String,
required: false,
},
agreedToTerms: {
type: Boolean,
required: [true, "Please provide a agreedToTerms"],
},
isDone: {
type: Boolean,
default: false, // 기본값으로 false 설정
},
date: {
type: Date,
default: () => {
let now = new Date();
now.setHours(now.getHours() + 9);
return now;
}, // 현재 날짜 및 시간으로 기본값 설정
},
});
DataSchema.methods.validateFields = function () {
const errors = this.validateSync();
if (errors) {
throw new Error(errors.errors);
}
};
export default (mongoose.models.inquiry ||
model<IData>("inquiry", DataSchema)) as Model<IData>;
// 1. model<IData>("inquiry", DataSchema)은 inquiry라는 모델의 이름으로 MongoDB 모델을 생성
// 2.만약 이미 같은 이름의 모델(inquiry)이 존재한다면 해당 모델을 사용하고, 존재하지 않으면 새로운 모델을 생성
이 코드는 Mongoose를 사용하여 MongoDB에 대한 데이터 모델을 정의하는 모듈입니다.
위의 예제에서는 요청값(body)의 각 필드의 필수값여부, 데이터 형식 등을 사전에 정의해둔것이죠.
( 이 메서드는 현재 데이터의 유효성을 검사하는 데 사용되며, 유효성 검사에서 오류가 발생하면 에러를 throw하도록 되어 있습니다. )
위에서 사용한 create 메서드는 MongoDB 모델 인스턴스에서 제공하는 메서드 중 하나이며, 신규 데이터를 생성하고 데이터베이스에 추가하는 데 사용된 것입니다!
🌱 마무리
간단한 프로젝트 예시를 통해 Next.js에서 서버리스 함수를 통한 API 라우팅 구현방식과, DB를 연결하는 방법에 대해 살펴보았습니다.
기존에는 서버 구축을 위해 별도로 node의 express 프레임워크 등을 이용하였는데, Next.js의 서버리스 함수(기능)을 통해 간편하고, 빠르게 웹을 제작할 수 있었습니다.