본문 바로가기

FrontEnd/React

[React] 프로그래밍으로 몽고DB 사용하기

프로젝트 환경 구축하기

 아래 명령어를 통해 package.json 파일을 서버 디렉터리에 생성한다.

> npm init --y

 

 

 타입스크립트를 개발 언어로 하는 Node.js 프로젝트는 항상 다음 3개의 패키지를 설치해 줘야한다.  @types/node 패키지는 setTimeout과 같은 자바스크립트 엔진이 제공하는 기능을 타입 스크립트에서 사용할 때 필요한 타입 라이브러리이다.

> npm i -D typescript ts-node @types/node

 

 다음으로는 tsconfig.json 설정 파일을 생성한다.

> tsc -- init

 

 타입스크립트 언어로 몽고DB를 사용하려면 mongodb라는 이름의 드라이버 패키지를 설치해야 한다.

> npm i mongodb
> npm i -D @types/mongodb

 

 

  위와 같이 설치를 진행했을 경우 package.json의 내용은 아래와 같다.

{
  "name": "ch07_2_server",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "description": "",
  "devDependencies": {
    "@types/mongodb": "^4.0.6",
    "@types/node": "^22.10.4",
    "ts-node": "^10.9.2",
    "typescript": "^5.7.2"
  },
  "dependencies": {
    "mongodb": "^6.12.0"
  }
}

 

 개발환경이 정상적으로 갖춰졌는지 src/index.ts를 생성해 실행해보자.

console.log('Hello world')

 

 생성한 index.js를 실행한다.

> ts-node src/index.js
Hello world

 

 

 

몽고DB와 연결하기

 프로그래밍으로 "mongodb://localhost:27017" URL 사용 시 몽고DB와 연결할 수 있다.  mongodb는 프로토콜 이름이고 localhost는 호스트이름, 기본 포트는 27017이다.

 

 mongodb 패키지는 몽고DB와 연결하기 위한 MongoClient 클래스를 제공하며 해당 클래스는 connect 정적 메서드를 제공해 반환값은 프로미스 형태로 MongoClient를 얻는다.

import {MongoClient} from 'mongodb'
static connect(url: string): Promise<MongoClient>

 

 몽고 DB 관련 유틸리티 함수를 만들기 위해 mongodb 디렉터리를 생성해 connectAndUseDB를 추가하자.

  • MongoClient : MongoDB 서버에 연결하는 주요 클래스이다.  이 객체를 통해 MongoDB 인스턴스에 연결을 생성한다.
  • Db : MongoDB 데이터베이스를 나타내는 클래스이다.  연결 후 특정 데이터베이스를 참조하기 위해 사용한다.
import { MongoClient, Db } from "mongodb";

export type MongoDB = Db;
export type ConnectCallback = (db: MongoDB) => void;

export const connectAndUseDB = async (
  callback: ConnectCallback,
  dbName: string,
  mongoUrl: string = "mongodb://localhost:27017"
) => {
  let connection;
  try {
    connection = await MongoClient.connect(mongoUrl);  // 몽고DB에 연결
    const db: Db = connection.db(dbName);              // 몽고쉘의 'use dbName'과 동일
    callback(db);                                      // db 객체를 콜백 함수의 매개변수로 호출
  } catch (e) {
    if (e instanceof Error) console.log(e.message);
  }
};

 

 

 생성된 connectAndUseDB 컴포넌트를 connectTest.ts 파일을 추가해 몽고DB가 정상적으로 연결되는지 확인하기 위해 파일을 추가한다.

import * as M from "../mongodb";

const connectDB = (db: M.MongoDB) => {
  console.log("db", db);
};

const connectTest = () => {
  M.connectAndUseDB(connectDB, "ch07");
};

connectTest(); // 연결 테스트 수행

 

 추가된 파일을 터미널을 통해 실행해보자.  몽고DB가 응답해 실행 결과를 출력하므로 연결이 정상적으로 수행된 것을 확인할 수 있다.  

> ts-node connectTest.ts
db Db {
  s: {
    options: {
      enableUtf8Validation: true,
      forceServerObjectId: false,
      pkFactory: [Object],
      raw: false,
      readPreference: [ReadPreference],
      retryWrites: true
    },
    ...(생략)...

 

 

 

컬렉션의 CRUD 메서드

 몽고 쉘에서는 'db.컬렉션_이름' 형태로 컬렉션에 접근할 수 있었고 프로그래밍에서는 'db.collection(컬렉션_이름)' 과 같은 형태로 접근할 수 있다.

 

 

 문서 생성 메서드 사용하기

  user 컬렉션에 insertOne과 insertMany 메서드를 사용해 데이터를 입력해보자.  다음은 두 메서드의 타입 선언문으로 둘 모두 프로미스 객체를 반환함을 알 수 있다.

insertOne(doc: OptionalUnlessRequiredId<TSchema>): Promise<InsertOneResult<TSchema>>
insertMany(doc: OptionalUnlessRequiredId<TSchema>[]): Promise<InsertManyResult<TSchema>>

 

 이 메서드를 통해 문서를 추가하기 위해 insertTest.ts 파일에 코드를 추가하자.

import * as M from "../mongodb";

const connectCB = async (db: M.MongoDB) => {
  try {
    const user = db.collection("user");
    try {
      await user.drop();
    } catch (error) {}

    const jack = await user.insertOne({ name: "Jack", age: 32 });
    console.log("jack", jack);
    const janeAndTom = await user.insertMany([
      { name: "Jane", age: 22 },
      { name: "Tom", age: 11 },
    ]);
    console.log("janeAndTom", janeAndTom);
  } catch (e) {
    if (e instanceof Error) console.log(e.message);
  }
};

const insertTest = () => {
  M.connectAndUseDB(connectCB, "ch07");
};

insertTest();

 

  실행결과도 몽고셸의 실행결과와 일치하는 것을 확인할 수 있다.

> ts-node insertTest.ts
jack {
  acknowledged: true,
  insertedId: new ObjectId('67778b9c1bcd4c334ac5a73d')
}
janeAndTom {
  acknowledged: true,
  insertedCount: 2,
  insertedIds: {
    '0': new ObjectId('67778b9c1bcd4c334ac5a73e'),
    '1': new ObjectId('67778b9c1bcd4c334ac5a73f')
  }
}

 

 

 

 문서 검색 메서드 사용하기

  findOne, find 메서드의 타입 선언문 프로미스 객체를 반환하는 findOne과 달리 find는 FindCursor 타입 객체를 반환한다.

findOne(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>
find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<withId<TSchema>>

 

  FindCursor 타입 객체는 toArray란 메서드를 제공하며 find의 반환값을 자바스크립트 배열로 바꿔준다.

const cursor = await find({})
const arrayResult = cursor.toArray()

 

  문서 검색을 테스트 하기 위해 findTest.ts 파일을 추가해 작성 후 실행해보자.

import * as M from "../mongodb";

const connectCB = async (db: M.MongoDB) => {
  try {
    const user = db.collection("user");
    const one = await user.findOne({});
    console.log("one", one);

    const many = await user.find({}).toArray();
    console.log("many", many);
  } catch (e) {
    if (e instanceof Error) console.log(e.message);
  }
};
const findTest = () => { M.connectAndUseDB(connectCB, "ch07"); };
findTest();

 

 findTest.ts 파일의 실행결과로 앞서 'user' 컬렉션에 삽입한 내용이 검색된 것을 확인할 수 있다.

> ts-node findTest.ts
one {
  _id: new ObjectId('67778b9c1bcd4c334ac5a73d'),
  name: 'Jack',
  age: 32
}
many [
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73d'),
    name: 'Jack',
    age: 32
  },
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73e'),
    name: 'Jane',
    age: 22
  },
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73f'),
    name: 'Tom',
    age: 11
  }
]

 

 

 

 문서 수정 메서드 사용하기

  아래 선언문은 컬렉션의 문서 수정 관련 메서드들의 타입 선언문으로 모두 프로미스 객체를 반환하는 걸 알 수 있다.

updateOne(filter:Filter<TSchema>, update:Updatefilter<TSchema>|Partial<TSchema>):
    Promise<UpdateResult>;
udpateMany(filter: Filter<TSchema>, update:Updatefilter<TScema>): Promise<UpdateResult | Document>;
findOneAndUpdate(filter:Filter<TSchema>, update:updateFilter<TSchema>, options: FindOneAndUpdateOptions):
    Promise<ModifyResult<TSchema>>;

 

 findOneAndUpdate 메서드를 호출 시 몽고셸은 returnNewDocument 속성 값을 true로 했지만, 프로그래밍에서는 returnDocument 속성 값을 'after'로 설정해야 한다.

const findOneResult = await.user.findOneAndUpdate(
    {name: 'John'},
    {$set: {age: 66}},
    {returnDocument: 'after'} // before or after
);

 

 문서 수정 메서드를 테스트 하기 위해 updateTest.ts 파일을 추가해 내용을 입력한다.

import * as M from "../mongodb";

const connectCB = async (db: M.MongoDB) => {
  try {
    const user = db.collection("user");

    await user.updateOne(
      { name: { $regex: /^J.*$/ } },
      { $set: { name: "John" }, $inc: { age: 10 } }
    );
    const updateOneResult = await user.find({}).toArray();
    console.log("updateOneResult", updateOneResult);

    await user.updateMany({ name: { $regex: /^J.*$/ } }, { $inc: { age: 10 } });
    const updateManyResult = await user.find({}).toArray();
    console.log("updateManyResult", updateManyResult);

    const findOneResult = await user.findOneAndUpdate(
      { name: "John" },
      { $set: { age: 66 } },
      { returnDocument: "after" }
    );
    console.log("findOneResult", findOneResult);
  } catch (e) {
    if (e instanceof Error) console.log(e.message);
  }
};

const updateTest = () => {
  M.connectAndUseDB(connectCB, "ch07");
};
updateTest();

 

  실행결과로 몽고셸에서 봤더 내용과 일치함을 확인할 수 있다.

> ts-node updateTest.ts
updateOneResult [
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73d'),
    name: 'John',
    age: 42
  },
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73e'),
    name: 'Jane',
    age: 22
  },
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73f'),
    name: 'Tom',
    age: 11
  }
]
updateManyResult [
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73d'),
    name: 'John',
    age: 52
  },
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73e'),
    name: 'Jane',
    age: 32
  },
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73f'),
    name: 'Tom',
    age: 11
  }
]
findOneResult {
  _id: new ObjectId('67778b9c1bcd4c334ac5a73d'),
  name: 'John',
  age: 66
}

 

 

 

 문서 삭제 메서드 사용하기

  다음은 컬렉션의 deleteOne과 deleteMany 메서드의 타입 선언문으로 두 메서드는 모두 프로미스 객체를 반환한다.

deleteOne(filter: Filter<TSchema>): Promise<DeleteResult>;
deleteMany(filter: Filter<TSchema>): Promise<DeleteResult>;

 

 

  문서 삭제를 테스트하기 위해 deleteTest.ts 파일을 추가해 내용을 넣고 실행해보자.

import * as M from "../mongodb";

const connectCB = async (db: M.MongoDB) => {
  try {
    const user = db.collection("user");

    const deleteOneResult = await user.deleteOne({ name: { $regex: /^J.*$/ } });
    console.log("deleteOneResult", deleteOneResult);

    const deleteManyResult = await user.deleteMany({
      name: { $regex: /^J.*$/ },
    });
    console.log("deleteManyResult", deleteManyResult);

    const userDocuments = await user.find({}).toArray();
    console.log("userDocuments", userDocuments);
  } catch (e) {
    if (e instanceof Error) console.log(e.message);
  }
};

const deleteTest = () => {
  M.connectAndUseDB(connectCB, "ch07");
};

deleteTest();

 

 

  실행결과 또한 앞서 본 몽고셸의 결과와 일치하는걸 확인할 수 있다.

> ts-node deleteTest.ts
deleteOneResult { acknowledged: true, deletedCount: 1 }
deleteManyResult { acknowledged: true, deletedCount: 1 }
userDocuments [
  {
    _id: new ObjectId('67778b9c1bcd4c334ac5a73f'),
    name: 'Tom',
    age: 11
  }
]