// endpoint
// api/short-links/[id].jsx
export default function handler(request, response) { // (req, res)
response.send("API");
}
// request.http
GET http://localhost:3000/api/short-links/123
export default function handler(req, res) {
res.send(req.query); // req.body, req.cookies, req.method
}
export default function handler(req, res) {
switch (req.method) {
case 'POST':
res.status(201).send({ // method chaining
title: '위키피디아 Next.js',
url: 'https://en.wikipedia.org/wiki/Next.js',
});
break;
case 'GET':
res.send([
{
id: 'abc',
title: '위키피디아 Next.js',
url: 'https://en.wikipedia.org/wiki/Next.js',
},
{
id: 'def',
title: '자유게시판',
url: 'https://codeit.kr/commnuity/general',
},
{
id: 'ghi',
title: '질문답변',
url: 'https://www.codeit.kr/commnuity/questions',
},
]);
default:
res.status(404).send();
}
}
/api/short-links로 들어오는 request를 처리하려면 /pages/api/shore-links.jsx 또는 /pages/api/short-links/index.jsx 경로로 파일을 만들고 아래처럼 함수를 default export하면 된다.
export default async function handler(req, res) {
...
}
| 프로퍼티 | 타입 | 설명 |
|---|---|---|
| method | 문자열 | request로 들어온 HTTP 메소드 값 |
| query | 객체 | 쿼리 스트링이나 Next.js에서 사용하는 Params 값이 들어 있는 객체 |
| body | 자유로움 | request의 바디 값 |
| cookie | 객체 | request의 쿠키가 key/value로 들어 있는 객체 |
함수 체이닝 방식으로 사용하기 때문에, res.status(201).send()처럼 함수를 이어서 사용할 수 있다.
| 프로퍼티 | 타입 | 설명 |
|---|---|---|
| status() | 함수 | response로 보낼 HTTP 상태 코드를 지정 |
| send() | 함수 | response로 보낼 바디를 전달 |
MongoDB Atlas는 MongoDB를 만드는 회사에서 운영하는 클라우드 서비스이다. 가입만하면 간편하게 클라우드로 MongoDB를 이용할 수 있다.
Mongoose라는 라이브러리는 쉽고 편리하기 때문에 MongoDB를 사용하는 자바스크립트 개발자들이 가장 많이 사용하는 라이브러리이다.
npm install mongoose
환경 변수는 프로그램에서 실행 환경에 따라 다른 값을 지정할 수 있는 변수이다. 꼭 웹 서비스가 아니더라도 사용하는 데이크톱 프로그램이나 터니멀에서도 이런 환경 변수를 많이 활용한다.
꼭 서버와 데이터베이스를 여러 개 쓰는 경우가 아니더라도 데이터베이스 주소와 같은 값을 소스코드에 그대로 쓰는 것은 위험하다. 반드시 환경 변수로 사용하는 것이 좋다. Node.js 환경에서는 이런 환경 변수들을 process.env라는 객체를 통해서 참조할 수 있다.
Next.js에서는 기본적으로 dotenv라는 파이브러리를 지원한다. 이 라이브러리는 .env 같은 이름의 파일에서 환경 변수들을 저장해 두면, Node.js 프로젝트를 실행할 때 환경 변수로 지정해 주는 라이브러리이다. 이 때 주의할 점은 .env 파일 같은 건 소스 코드에 포함시키면 안 된다는 것이다.
Next.js 프로젝트에서는 기본적으로 dotenv 설정이 되어 있어서, .env.local 같은 파일을 추가하면 손쉽게 환경 변수를 추가할 수 있다.
// .env.local
MONGODB_URI=mongodb+srv://admin:blahblah@.clusterName.blahblah.mongodb.net/databaseName?retryWrites=true&w=majority
이렇게 추가한 값을 process.env.MONGODB_URI로 참조할 수 있다.
// pages/api/short-links/index.jsx
export default function handler(req, res) {
const DB_URI = process.env.MONGODB_URI;
// 데이터베이스 접속 ...
}
위에서 추가한 데이터베이스 주소는 유저의 아이디와 비밀번호를 포함한 아주 민감한 정보이다. 이러한 환경 변수가 웹 사이트에 노출되는 것을 막기 위해서 Next.js에서는 클라이언트 사이드에서 사용하는 환경 벼수에 특별한 접두사(prefix)를 사용한다. NEXT_PUBLIC_이라고 이름을 붙이면 이 환경 변수는 클라이언트 사이드에서도 사용할 수 있다. 예를 들어 클라이언트 사이드에서 현재 사이트의 호스트 주소를 저장해 두고 참조하고 싶다면 NEXT_PUBLIC_HOST라는 이름으로 사용하면 된다.
MONGODB_URI=mongodb+srv://admin:blahblah@cluster0.blahblah.mongodb.net/databaseName?retryWrites=true&w=majority
NEXT_PUBLIC_HOST=http://localhost:3000
export default Home() {
// 페이지 컴포넌트에서는 아래와 같이 사용
return (
<>호스트 주소: {process.env.NEXT_PUBLIC_HOST}</>
);
//db/dbConnect.jsx
import mongoose from "mongoose";
declare global {
var mongoose: any; // This must be a `var` and not a `let / const`
}
const MONGODB_URI = process.env.MONGODB_URI!;
if (!MONGODB_URI) {
throw new Error(
"Please define the MONGODB_URI environment variable inside .env.local",
);
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
return mongoose;
});
}
try {
cached.conn = await cached.promise;
} catch (e) {
cached.promise = null;
throw e;
}
return cached.conn;
}
export default dbConnect;
.env.local 파일도 지정해 주어야 한다.
// db/models/ShortLinks.jsx
import mongoose from 'mongoose';
const shortLinkSchema = new mongoose.Schema( // 모델의 구조와 프로퍼티를 정하는 것
{
title: { type: String, default: '' },
url: { type: String, default: '' },
shortUrl: { type: String, default: '' },
}, {
timestamps: true,
}
);
const ShortLink = mongoose.models['ShortLink'] || mongoose.model('ShortLink', shortLinkSchema)
export deafult ShortLink;
// api/short-links/index.jsx
export default async function handler(req, res) {
await dbConnect();
switch (req.method) {
case 'POST':
const newShortLink = await ShortLink.create(req.body);
res.status(201).send(newShortLink);
break;
case 'GET':
const shortLinks = await ShortLink.find();
res.send(shortLinks)
break;
default:
res.status(404).send();
}
}
// /api/short-links/[id].jsx
export default async function handler(req, res) {
await dbConnect();
const { id } = req.qeury;
switch(req.method) {
...
case 'GET':
const shortLink = await ShortLink.findById(id);
res.send(shortLink);
break;
...
}
// [id].jsx
export default async function handler(req, res) {
await dbConnect();
const { id } = req.qeury;
switch(req.method) {
case 'PATCH':
const updatedShortLink = await ShortLink.findByIdAndUpdate(id, req.body, {
new: true,});
res.send(updatedShortLink);
...
case 'DELETE':
await ShortLink.findByIdAndUpdate(id);
res.status(204).send();
break;
}
}
가장 먼저 mongoose.connect() 함수를 사용해소 커넥션을 만들고 사용한다. 이때 Next.js 환경에서 커넥션을 불필요하게 여러 개 만들 수 있기 때문에, 캐싱 기법을 사용한다. 복잡할 수 있기 때문에 공식 레포지토리에 있는 코드(next.js/examples/with-mongodb-monggose)를 참고해서 구현하는 걸 권장한다.
연동이 잘 되었는지 확인하려면 mongoose.connection.readyState라는 값으로 확인할 수 있다. 문서에 따르면 접속한 상태에서는 1이라는 값이 출력된다.
import mongoose from 'mongoose';
// ...
await dbConnect();
console.log(mongoose.connection.readyState);
mongoose.Schema()를 사용해서 스키마를 생성한다. 스키마는 모델이 어떤 속성을 가질지 정하는 용도이다. mongoose.model()을 사용해서 모델을 생성한다. 이때 모델의 이름을 첫 번째 아규먼트로 넘겨주는데, 이 이름은 mongoose.models[ ... ]로 참조할 수 있기 때문에 잘 지정했는지 확인해야 한다.
모듈 파일을 import할 때마다 모델을 생성하는 일이 일어나지 않도록 mongoose.models['ShortLink' || mongoose.model('ShortLink', shortLinkSchema)처럼 작성해 둔 부분도 확인해보면 된다.
import mongoose from 'mongoose';
const shortLinkSchema = new mongoose.Schema(
{
title: { type: String, default: '' },
url: { type: String, default: '' },
shortUrl: { type: String, default: '' },
},
{
timestamps: true,
}
);
const ShortLink =
mongoose.models['ShortLink'] || mongoose.model('ShortLink', shortLinkSchema);
export default ShortLink;
아규먼트로 전달한 값으로 도큐먼트를 생성한다.
const newShortLink = await ShortLink.create({
title: '코드잇 커뮤니티',
url: 'https://www.codeit.kr/community/general',
});
조건에 맞는 모든 도큐먼트를 조회한다. 이때 조건으로 쓰이는 객체는 MongoDB의 문법을 따른다. 간단하게는 속성과 값을 key와 value로 하는 객체를 넣어줄 수 있다.
const shortLinks = await ShortLink.find(); // 모든 도큐먼트 조회
const filteredShortLinks = await ShortLink.find({ shortUrl: 'c0d317' }) // shortUrl 값이 'c0d317'인 모든 도큐먼트 조회
아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 조회한다.
const shortLink = await ShortLink.findById('n3x7j5');
첫 번째 아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.
const updatedShortLink = await ShortLink.findByIdAndUpdate('n3x7j5', { ... });
아규먼트로 넘겨준 아이디에 해당하는 도큐먼트를 삭제한다.
await ShortLink.findByIdAndDelete('n3x7j5');
아규먼트로 조건을 넘겨주고 해당하는 도큐먼트를 하나만 조회한다.
const shortLink = await ShortLink.findOne({ shortUrl: 'c0d317' });
첫 번째 아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 업데이트한다. 두 번째 아규먼트로는 업데이트할 값을 넘겨준다.
await ShortLink.updateOne({ _id: 'n3x7j5' }, { ... }); // 업데이트만 하고 업데이트 된 값을 리턴하지는 않음
const updatedShortLink = await ShortLink.findOneAndUpdate({ _id: 'n3x7j5' }, { ... });
아규먼트로 넘겨준 조건에 해당하는 도큐먼트를 삭제한다.
await ShortLink.deleteOne({ _id: 'n3x7j5' }, { ... }); // 삭제만 하고 기존 값을 리턴하지는 않음
const deletedShortLink = await ShortLink.findOneAndDelete({ _id: 'n3x7j5' }, { ... });
// pages/index.jsx
...
async function handleCreate(e) {
e.prevendDefault();
const res = await axios.post('/short-link/', { title: url, url });
const newShortLink = res.data;
const newShortUrl = newShortLink.shortUrl;
setShortUrl(newShortUrl);
}
...
// short-links/new.js
export default function ShortLinkCreatePage() {
const router = useRouter();
async function handleSubmit(values) {
await axios.post('/short-links/', values);
router.push('/short-links/');
}
...
}
// [shortUrl].jsx
export async function getServerSideProps(context) {
const { shortUrl } = context.query;
await dbConnect();
const shortLink = await ShortLink.findOne({ shortUrl });
if (shortLink) {
return {
redirect: {
destination: shortLink.url,
permanent: false,
},
};
}
return {
notFound: true,
};
}
export default function ShortUrlPage() {
return null;
}
// short-links/index.jsx
export async function getServerSideProps() {
await dbConnect();
const shortLinks = awiat ShortLink.find();
return {
props: {
shortLinks: JSON.parse(JSON.stringify(shortLinks)),
}
}
}
export default function ShortLinkListPage({ shortLinks }) {
return (
...
)
}
// [id].jsx
export async function getServerSideProps(context) {
const { id } = context.query;
await dbConnect();
const shortLink = await ShortLink.findById(id);
if (shortLink) {
return {
props: {
shortLink: JSON.parse(JSON.stringify(shortLink)),
},
};
}
return {
notFound: true,
};
}
export default function ShortLinkEditPage({ shortLink }) {
const router = useRouter();
const { id } = router.query;
async function handleSubmit(values) {
await axios.patch(`/short-links/${id}`, values);
router.push("/short-links/");
}
...
}
// index.jsx
export default function ShortLinkListPage({ shortLinks: initialShortLinks }) {
const [shortLinks, setShortLinks] = useState(initialShortLinks);
async function handleDelete(id) {
await axios.delete(`/short-links/${id}`);
setShortLinks((prevShortLinks) =>
prevShortLinks.filter((shortLink) => shortLink._id !== id)
);
}
...
NEXT_PUBLIC_BASE_URL 환경 변수는 추가하고, MONGOOB_URI는 따로 지정하지 않는다.)0.0.0.0/0을 허용한다는 것을 이해했다고 체크한다.