안녕하세요
MongoDB 타입 설정 + DB에서 게시물 요청 (필드 제한하기) 본문
목차
1. Next.js에서 MongoDB 연결하기
- 설치
- 연결
- Type 세팅
2. DB collection에 데이터 삽입 및 출력
3. DB에서 요청한 데이터로 게시물 목록, 게시물 상세 구현
- 요청할 데이터의 필드 제한하기
- collection Type 에러 해결
- Type extends
- findOne Type 에러 해결
1. MongoDB와 타입스크립트를 쓰기 위해 필요한 설정을 해봅시다.
MongoDB를 설치합니다.
npm install mongodb
타입스크립트로 진행하기 위해서는 mongodb에도 타입을 지정해주어야 합니다.
MongoDB 에 연결하기 위해서는 다음과 같이 작성하면 됩니다.
// src/utils/db/db.ts
import { MongoClient } from "mongodb";
const url: string | undefined = process.env.DB_CONNECT;
// 환경변수 설정하지 않으면 에러 발생
if (!url) {
throw new Error("The MONGODB_URL environment variable is not defined");
}
let connectDB: Promise<MongoClient>;
// 개발 단계에서는 global 변수에 저장하여 connect를 저장할 때마다 반복하지 않도록 함
if (process.env.NODE_ENV === "development") {
if (!global._mongo) {
global._mongo = new MongoClient(url).connect();
}
connectDB = global._mongo;
} else {
connectDB = new MongoClient(url).connect();
}
export { connectDB };
여기서 필요한 타입은 MongoClient 와 global._mongo입니다.
/src/types 디렉토리에 global 타입을 모아둘 파일을 생성합니다.
// src/types/global.d.ts
export {};
declare global {
var _mongo: Promise<MongoClient> | undefined;
}
global.d.ts
* 파일명에서 .d.ts는 타입 선언 파일로 취급한다는 의미입니다.
해당 파일에는 타입 정의만이 들어갈 수 있게 됩니다.
export {};
* 이 부분은 파일을 모듈 처리하도록 하여 격리된 스코프를 가지도록 합니다.
declare global {
var _mongo: Promise | undefined;
}
* 전역 속성인 global._mongo 의 타입을 글로벌 스코프에서 지정해줍니다.
루트 디렉토리의 tsconfig.json 파일에 다음 속성을 추가해줍니다.
{
...
// TypeScript가 타입 정의 파일을 검색할 위치
"typeRoots": ["./src/types", "./node_modules/@types"],
// TypeScript 컴파일 대상 설정
"include": [..., ".next/types/**/*.ts", "src/**/*", "global.d.ts"],
}
typeRoots
타입을 검색할 경로를 지정합니다.
include
TypeScript 컴파일러가 컴파일할 JavaScript 파일 범위를 경로로 지정합니다.
똑같이 설정을 했다면 타입 경고가 해결되어야 합니다.
2. connectDB를 import하여 collection의 도큐먼트를 출력해봅시다.
import { connectDB } from "@/utils/db/db";
const Home = async () => {
const db = (await connectDB).db("blog");
let result = await db.collection("post").find().toArray();
console.log(result);
return <div>메인</div>;
};
export default Home;
db.ts에서 export한 connectDB에는 DB와 연결된 MongoClient 객체가 할당되어 있습니다.
blog DB의 post collection에 있는 documents를 출력할 것입니다.
const Home = async () => {
db에 연결하는 작업은 비동기로 실행되어야 합니다.
const db = (await connectDB).db("blog");
비동기로 connectDB를 호출하고, blog DB에 연결시켜 줍니다.
let result = await db.collection("post").find().toArray();
post collection의 도큐먼트들을 불러옵니다.
다음과 같이 임시 데이터를 post collection에 삽입하고 출력해보겠습니다.
🔻삽입할 데이터, insert 방법 펼쳐보려면🔻
올바르게 삽입이 되었다면 출력을 해봅시다.
const result = await postCollection.find().toArray();
console.log(result);
🔻출력 결과 펼쳐보려면🔻
[
{
_id: new ObjectId("65002c07a382eb0e8de06ae1"),
id: 1,
src: 'https://cdn.pixabay.com/photo/2023/08/29/19/09/starling-8221990_640.jpg',
title: '제목제목제목제목제목제목제목제목제목제목제목제목',
subtitles: [ '#페이지네이션', '#레이지로딩', '#예약', '#페이지네이션', '#레이지로딩', '#예약' ],
languages: [ '#JavaScript', '#Rust', '#Go', '#JavaScript', '#Rust', '#Go' ],
content: 'ㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ',
commentCount: 10,
author: 'young',
date: 2023-09-12T09:14:47.969Z,
likes: 7
},
{
_id: new ObjectId("65002c07a382eb0e8de06ae2"),
id: 2,
src: 'https://cdn.pixabay.com/photo/2023/08/26/18/01/planet-8215532_640.png',
title: '제목',
subtitles: [ '#페이지네이션', '#예약' ],
languages: [ '#JavaScript', '#Rust', '#Go' ],
content: 'ㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ',
commentCount: 10,
author: 'young',
date: 2023-09-12T09:14:47.969Z,
likes: 7
},
{
_id: new ObjectId("65002c07a382eb0e8de06ae3"),
id: 3,
src: 'https://cdn.pixabay.com/photo/2023/09/04/13/17/mushrooms-8232731_1280.jpg',
title: '제목',
subtitles: [ '#레이지로딩', '#예약' ],
languages: [ '#JavaScript', '#Rust', '#Go' ],
content: 'ㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ',
commentCount: 10,
author: 'young',
date: 2023-09-12T09:14:47.969Z,
likes: 7
},
{
_id: new ObjectId("65002c07a382eb0e8de06ae4"),
id: 4,
src: 'https://cdn.pixabay.com/photo/2023/05/14/17/46/ducklings-7993465_1280.jpg',
title: '제목',
subtitles: [ '#예약' ],
languages: [ '#JavaScript', '#Rust', '#Go' ],
content: 'ㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ',
commentCount: 10,
author: 'young',
date: 2023-09-12T09:14:47.969Z,
likes: 7
},
{
_id: new ObjectId("65002c07a382eb0e8de06ae5"),
id: 5,
src: 'https://cdn.pixabay.com/photo/2023/08/28/23/17/superb-fairywren-8220199_640.jpg',
title: '제목',
subtitles: [ '#드래그앤드롭' ],
languages: [ '#JavaScript', '#Rust', '#Go' ],
content: 'ㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ\nㅇㅇㅇㅇㅇㅇㅇ',
commentCount: 10,
author: 'young',
date: 2023-09-12T09:14:47.969Z,
likes: 7
}
]
요청한 데이터로 post cards를 구현해봅시다.
이제 서버에서 불러온 데이터를 통해서 게시물을 출력할 수 있게 되었습니다.
* type에러가 발생할 것입니다.
collection을 지정할 때 type을 collection에 함께 지정하면 schema처럼 특정 타입만을 포함한 게시물인지 확인을 할 수 있게 됩니다.
import { Post } from "@/types/post";
const Category = async () => {
const db = (await connectDB).db("blog");
let postCollection = db.collection<Post>("post");
const postData: Post[] = await postCollection.find().toArray();
return (
...
let postCollection = db.collection<Post>("post");
post 콜렉션의 파일은 Post 타입이어야만 합니다.
const postData: Post[] = await postCollection.find().toArray();
postData에는 find().toArray() 메서드를 통해 불러온 데이터는 배열이 될 것이기 때문에 Post[] 타입이 되어야 합니다.
필드 제한하기
card에는 content가 포함되지 않습니다. 하지만 content에는 많은 데이터가 포함됩니다.
따라서 card에서는 content를 제외하고 데이터를 요청하도록 하여 DB와 클라이언트의 메모리를 절약할 수 있도록 합시다.
// _id 필드는 노출되지 않는 것이 좋습니다. content 필드는 제외하고 도큐먼트를 불러옴으로써 card에 불필요한 데이터는 제외합니다.
const postData: Post[] = await postCollection.find({}, { projection: { content: 0, _id: 0 } }).toArray();
find({}, { projection: { content: 0, _id: 0 } })
find({}) 메서드는 모든 데이터를 가져올 때 이용되는데,
두번째 속성에 projection 을 이용하여 제외할 필드를 선택할 수 있습니다. content: 0 과 같이 작성합니다.
(_id 는 MongoDB에서 도큐먼트 고유의 id를 자동으로 할당한 것이며, 이는 클라이언트에게 불필요한 정보입니다.
민감한 정보를 포함한 ObjectID의 경우에는 특히나 노출되지 않도록 주의하여야 합니다.)
이렇게 필드를 제한하고 출력을 해보면 _id와 content 필드가 제외된 데이터를 가져온 것을 볼 수 있습니다.
{
id: 5,
src: 'https://cdn.pixabay.com/photo/2023/08/28/23/17/superb-fairywren-8220199_640.jpg',
title: '제목',
subtitles: [ '#드래그앤드롭' ],
languages: [ '#JavaScript', '#Rust', '#Go' ],
commentCount: 10,
author: 'young',
date: 2023-09-12T09:14:47.969Z,
likes: 7
}
Card 타입과 Post 타입을 구분하면 좋겠죠
export interface Card {
id: number;
src: string;
title: string;
subtitles: string[];
languages: string[];
author: string;
date: Date;
commentCount: number;
likes: number;
}
export interface Post extends Card {
content: string;
}
Card 타입에는 content가 포함되어 있지 않고,
Post 타입은 Card 타입을 extends하여 content를 갖도록 합니다.
요청한 데이터로 post detail 페이지를 구현해봅시다.
const Post = async ({ postId }: any) => {
const db = (await connectDB).db("blog");
const postCollection = db.collection<Post>("post");
const postData: Post | null = await postCollection.findOne(
{ id: Number(postId) },
{ projection: { _id: 0 } }
);
return (
...
* findOne 메서드를 이용하면 "withid..." 어쩌고 하는 type에러가 발생할 것입니다.
const postData: Post | null = await postCollection.findOne(
위와 같이 타입에 null 타입을 함께 지정해주어야 합니다.
존재하지 않는 id일 수 있기 때문입니다.
'Next13 블로그 프로젝트' 카테고리의 다른 글
게시물 수정 기능 구현, 권한 없는 이용자 접근 제한 방법 (0) | 2023.09.19 |
---|---|
React-Quill로 게시물 편집, DB에 저장하기 (0) | 2023.09.18 |
Middleware로 권한이 없는 유저 "/"로 redirect 시키기 (0) | 2023.09.15 |
블로그 프로젝트 게시물 레이아웃 (+라우팅) (0) | 2023.09.10 |
나만의 Next.js Blog 프로젝트 구상 (0) | 2023.09.09 |