데이터베이스 : 데이터를 따로 저장하고 관리하는 프로그램
MongoDB Atlas 사용법
현재 폴더에서 터미널을 열고 아래 명령어를 입력해서 패키지를 설치한다.
npm install mongoose
수많은 서버와 데이터베이스를 연결할 때 사용하는 것을 환경변수라고 한다.
코드 상에는 MONGODB_URI
와 같은 값으로 데이터베이스 주소를 지정해 두고, 각 서버를 실행할 때마다 다른 데이터베이스 주소를 넣어준다.
환경 변수는 프로그램에서 실행 환경에 따라 다른 값을 지정할 수 있는 변수이다.
서버와 데이터베이스를 여러 개 쓰는 경우가 아니더라도 데이터베이스 주소와 같은 값을 소스코드에 그대로 쓰는 것은 위험하므로 반드시 환경 변수로 사용하는 것이 좋다.
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
로 참조할 수 있다.
export default function handler(req, res) {
const DB_URI = process.env.MONGODB_URI;
// 데이터베이스 접속 ...
}
환경 변수가 웹 사이트에 노출되는 사고를 막기 위해서 Next.js에서는 클라이언트 사이드에서 사용하는 환경 변수에 특별한 접두사를 사용한다.
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.js
import mongoose from "mongoose";
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;
MONGODB URI는 환경변수에서 가져와서 쓰고 있다.
MONGO Atlas로 접속해서 Connect->Drivers에 있는 application code를 복사하고, .env.local
파일을 만들어
MONGODB_URI= 복사한거 붙여넣기
따로 메모해둔 유저네임과 비밀번호도 넣어준다.
주고 뒷부분의 경로 부분에 원하는 데이터베이스 이름도 적어준다.
cluster0.haezxz3.mongodb.net/shortit
-> 데이터베이스 이름을 shortit이라고 적은 예시
pages/api/short-links/index.js
import dbConnect from "@/db/dbConnect";
import mongoose from "mongoose";
export default async function handler(req, res) {
await dbConnect();
console.log(mongoose.connection.readyState);
switch (req.method) {
case "PATCH":
res.send("ShortLink 수정");
break;
case "GET":
res.send("ShortLink 조회");
break;
default:
res.send();
break;
}
}
다음과 같이 dbConnect()와 콘솔로그를 사용하여 연동을 했다.
request.http
POST http://localhost:3000/api/short-links/
Content-Type: application/json
{
"title":"codeit"
}
이 리퀘스트를 실행하면
콘솔에 1이 찍힌다. (connected 되었다는 의미)
틀을 미리 만들어두고 데이터를 부어서 사용하기 편하게 만드는 것
데이터를 참조하는 것 말고도 데이터베이스에 있는 데이터를 수정하거나 삭제하는 등 다양한 작업을 편하게 할 수 있다.
mongoose에서 모델을 만들려면 일단 스키마라는 것을 만든다.
Schema는 모델의 구조와 프로퍼티를 정하는 것
mongoose.model
이용해서 모델을 만든다.
첫번째 아규먼트가 모델의 이름, 이 이름은 mongoose.models[]
프로퍼티에서 활용한다.
db/models/ShortLink.js
import mongoose from "mongoose";
const shortLinkSchema = new mongoose.Schema({
title: { type: String, default: "" },
url: { type: String, default: "" },
shortUrl: { type: String, default: "" },
});
const ShortLink =
mongoose.models["ShortLink"] || mongoose.model("ShortLink", shortLinkSchema);
export default ShortLink;
위 코드에서는 mongoose.models
에 있는지 확인하고 없다면 모델을 생성한다.
모델을 여러번 만들지 않으려고 이런 구조를 사용
생성날짜, 수정날짜는 몽구스에서 편하게 추가할 수 있다.
const shortLinkSchema = new mongoose.Schema(
{
title: { type: String, default: "" },
url: { type: String, default: "" },
shortUrl: { type: String, default: "" },
},{
timestamps: true,
}
);
이렇게 timestamps: true를 추가해주면 된다.
아무 엔드포인트에 가서
console.log(ShortLink);
콘솔로그를 출력해 모델이 잘 생성되었는지 확인할 수 있다.
req.body
에는 다양한 값들이 들어올 수 있지만 기본적으로 몽구스에서는 우리가 만든 스키마에 맞지 않는 값들은 무시하기 때문에 그대로 써도 된다.
데이터베이스에 저장하는 작업은 비동기 작업이기 때문에 await로 기다려준다.
pages/api/short-links
import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";
export default async function handler(req, res) {
await dbConnect();
console.log(ShortLink);
switch (req.method) {
case "POST":
const newShortLink = await ShortLink.create(req.body);
res.status(201).send(newShortLink);
break;
리퀘스트로
POST http://localhost:3000/api/short-links/
Content-Type: application/json
{
"title":"codeit",
"url":"https://codeit.kr"
}
POST 요청을 보내주면 생성된 데이터가 리스폰스로 온다.
리스폰스로 온 것들 중에
_id
는 MongoDB에서 데이터를 서로 구분해주기 위해 만들어주는 아이디이고,
__v
는 monggose가 내부적으로 활용하는 값이다.
pages/api/short-links/[id].js
import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";
export default async function handler(req, res) {
await dbConnect();
const { id } = req.query;
switch (req.method) {
case "GET":
const shortLink = await ShortLink.findById(id);
res.send(shortLink);
break;
case "PATCH":
res.send("ShortLink 수정");
default:
res.send();
break;
}
}
request.http
GET http://localhost:3000/api/short-links/POST 요청으로 받은 아이디
참고) Request 사이에는 ###로 구분을 해줘야 한다.
pages/api/short-links
import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";
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();
}
}
request.http
GET http://localhost:3000/api/short-links/
리퀘스트를 보내면 아까 만들었던 도큐먼트가 배열 안에 담겨서 온다.
수정하는 것은 findByIdAndUpdate(id, 업데이트할 데이터)
를 사용하면 된다.
findByIdAndUpdate
이 함수의 리턴값은 수정하기 전의 도큐먼트 값이다. 그래서 세번째 아규먼트에 옵션을 추가해서 new: true로 만들어준다.
findByIdAndUpdate(id, 업데이트할 데이터, {new: true})
이러면 업데이트 된 도큐먼트가 잘 도착한다.
pages/api/short-links/[id].js
import dbConnect from "@/db/dbConnect";
import ShortLink from "@/db/models/ShortLink";
export default async function handler(req, res) {
await dbConnect();
const { id } = req.query;
switch (req.method) {
case "GET":
const shortLink = await ShortLink.findById(id);
res.send(shortLink);
break;
case "PATCH":
const updatedShortLink = await ShortLink.findByIdAndUpdate(id, req.body, {
new: true,
});
res.send(updatedShortLink);
break;
default:
res.send();
break;
}
}
request.http
PATCH http://localhost:3000/api/short-links/6667d9059b6b14451796c3a8
Content-Type: application/json
{
"title":"edit"
}
findByIdAndDelete(id)
함수를 사용한다.
case "DELETE":
await ShortLink.findByIdAndDelete(id);
res.status(204).send();
break;
DELETE http://localhost:3000/api/short-links/6667f7549b6b14451796c3b5
아규먼트로 조건을 넘겨주고 해당하는 도큐먼트를 하나만 조회한다.
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' }, { ... });