Mongodb는 docker로 구성하고, Node는 Mac에 설치하여 접근하다.
지난번에 스터디하면서 만들었던 docker-compose.yml를 그대로 사용한다.
# Use root/example as user/password credentials
version: '3.1'
services:
mongo:
image: mongo:6.0.2
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
volumes:
- ~/mongo-storage:/data/db
ports:
- 27017:27017
networks:
- backend
mongo-express:
image: mongo-express
restart: always
ports:
- 27018:8081
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: root
ME_CONFIG_MONGODB_ADMINPASSWORD: example
ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
depends_on:
- mongo
networks:
- backend
networks:
backend:
driver: bridge
external: true
internal: true
docker-compose up -d
로 mongo 서버 실행
testdb를 사용할 예정이다. 접근하기위해서는 testdb에 접근할 user/pwd를 생성해야 한다. mongosh가 db접속을 위한 클라이언트 실행 이름이다.
dajkim76@Kims-Mac-mini ~/docker/backend-mongo % docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d891f0402d03 mongo-express "tini -- /docker-ent…" 2 weeks ago Up 53 minutes 0.0.0.0:27018->8081/tcp backend-mongo-mongo-express-1
745448f94859 mongo:6.0.2 "docker-entrypoint.s…" 2 weeks ago Up 53 minutes 0.0.0.0:27017->27017/tcp backend-mongo-mongo-1
dajkim76@Kims-Mac-mini ~/docker/backend-mongo % docker exec -i -t backend-mongo-mongo-1 bash
root@745448f94859:/# mongosh -u root -p example
Current Mongosh Log ID: 63688a63565e9961937cc45a
Connecting to: mongodb://<credentials>@127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.6.0
Using MongoDB: 6.0.2
Using Mongosh: 1.6.0
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
test> db.createUser( { user: "testuser", pwd: "testpwd", roles: [ { role: "readWrite", db: "testdb" }, ] } )
{ ok: 1 }
노드에서 연결 테스트..
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const MONGO_HOST = "localhost";
const MONGO_URI = `mongodb://testuser:testpwd@${MONGO_HOST}:27017/testdb`;
function onMongoConnected() {
console.log('Successfully connected to mongodb')
const Cat = mongoose.model('Cat', { name: String });
const kitty = new Cat({ name: 'Zildjian' });
kitty.save().then(() => console.log('meow'));
}
console.log('Connecting mongo server')
mongoose.connect(MONGO_URI)
.then(onMongoConnected)
.catch(e => console.error(e));
Node js에서 접근시 인증 에러가 난다. MongoServerError: Authentication failed.
테스트 삼아서..
test> db.createUser( { user: "testuser_1", pwd: "testpwd_1", roles: [ { role: "readWrite", db: "testdb" }, ] } )
{ ok: 1 }
서버에 adbmin db에저장된 document를 보면
roles에 db는 testdb로 저장되어 있지만 위에 db는 "test"로 저장되어 있는데 이게 문제 인듯하다. 위에 db는 createUser시에 파라미터로 넣을 수 없다. 넣으면 에러난다. 따라서 mongo-express에서 document를 "testdb"로 수정하니 인증 오류가 해결되었다. 왜 이런 문제가 생기는지는 잘 모르겠다. db이름에 끝에 db가 들어가서 문제가 되는듯..
2022/11/14일 추가
testdb를 선택하지 않아 생기는 문제였다. use testdb 하고 나서 createUser하면 정상적으로 유저가 등록된다...
test> use testdb
switched to db testdb
testdb> db.createUser( { user: "testuser_2", pwd: "testpwd_2", roles: [ { role: "readWrite", db: "testdb" }, ] } )
{ ok: 1 }
testdb>
Nest JS로 테스트 서버 만들기
Nest jS는 가이드는 https://docs.google.com/presentation/d/1HKl1_lRTfe2RktueA3rWZ50XUUH1DZO6Nre8Ioeb1Os/edit?usp=sharing 이 문서를 참고..
서버 시작시 db 연결하기
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const MONGO_HOST = "localhost";
const MONGO_URI = `mongodb://testuser:testpwd@${MONGO_HOST}:27017/testdb`;
mongoose.connect(MONGO_URI)
.then(() => console.log('Successfully connected to mongodb'))
.catch(error => console.error(error))
console.log('Starting nest server port: 3000')
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
db 모델을 하나 정의
db_models.ts
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type :String,
},
},{collection:'user_list',
versionKey: false
});
export const User = mongoose.model('User', userSchema)
localhost:3000/hello/홍길동
을 입력하면 user_list 콜렉션에 저장되도독
app.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('/hello/:name')
getHello(@Param('name') name: string): string {
return this.appService.getHello(name);
}
}
app.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './db_models';
@Injectable()
export class AppService {
getHello(name: string): string {
const user = new User({ name: name });
user.save().then(() => console.log(name + ' saved'));
return 'Hello! ' + name;
}
}
auto increment 자체 구현
서버 db는 여러 유저의 데이타를 가지고 있는데, 유저마다 db를 생성할 수 없기 때문에, collection마다 이 documnet가 누구의 db인지를 구분할는 필드를 넣었다. 유저를 구분하는 용도로 did를 사용한다.
cid는 collection별로 고정된 값을 가진다. 하나의 콜렉션에서 유저별로 서로 다른 고유하게 증가하는 sequence를 만들기 위한 테스트 작업이다.
db_models.ts 일부 추가
const tsListSchema = new mongoose.Schema({
did: { type: Number, require: true}, // db id
cid: { type: Number, require: true}, // collection id
ts: { type: Number, default: 0 }
},{ collection:'ts_list',
versionKey: false
});
const TsList = mongoose.model('TsList', tsListSchema);
// ref: https://mongoosejs.com/docs/tutorials/findoneandupdate.html
export async function getNextTs(cid: number, did: number) : Promise<Number | undefined> {
const doc = await TsList.findOneAndUpdate({cid: cid, did: did}, {$inc: { ts: 1} }, {new: true, upsert: true}).exec()
return doc.ts
}
cid와 did가 일치한 ({cid: .. , did: ...})
document를 찾아서 ts를 1 증가시키고 ($inc ...)
없으면 새로 만든다.(new: true)
ts가 증가된 document를 리턴한다. (upsert: true)
app.controller.ts
@Get('/hello/:name')
async getHello(@Param('name') name: string): Promise<string> {
return this.appService.getHello(name);
}
async를 지원하도록 수정한다.
app.service.ts
import { Injectable } from '@nestjs/common';
import { User , getNextTs} from './db_models';
@Injectable()
export class AppService {
async getHello(name: string): Promise<string> {
const nextTs = await getNextTs(1, 1)
console.log("nextTs:" + nextTs)
const user = new User({ name: name, did: 1, ts: nextTs });
user.save().then(() => console.log(name + ' saved'));
return 'Hello! ' + name;
}
}
_id 대신에 Number 타입의 id를 쓰기. auto increment와 연계해서 만들 수는 있으나 _id 입력시 필드는 unique해야 한다
const userSchema = new mongoose.Schema({
// _id: ObjectId('6368d64ea66f9be19eee81c2'), 대신에 Number를 쓰겠다.
_id: {
type: Number
},
name: {
type :String,
},
did: {
type: Number,
},
ts: {
type: Number,
}
},{_id: false, //_id: ObjectId('6368d64ea66f9be19eee81c2'), 형태는 쓰지 않겠다.
collection:'user_list',
versionKey: false
});
_id는 그대로 두고, id를 새로 만든다. id는 중복가능하지만 did와 id의 조합은 unique하다. 이것을 createIndex 문으로 만들 수 있다. mongosh에서
db.id_list.createIndex( {cid: 1, did: 1}, { unique: true } )
db.user_list.createIndex({id: 1, did: 1}, { unique: true } )
const userSchema = new mongoose.Schema({
id: {
type: Number
},
name: {
type :String,
},
did: {
type: Number,
},
ts: {
type: Number,
}
},{
collection:'user_list',
versionKey: false
});
const nextIdSchema = new mongoose.Schema({
did: { type: Number, require: true}, // db id
cid: { type: Number, require: true}, // collection id
id: { type: Number, default: 0 }
},{ collection:'id_list',
versionKey: false
});
const NextIdCollection = mongoose.model('NextId', nextIdSchema);
export async function getNextId(cid: number, did: number) : Promise<Number | undefined> {
const doc = await NextIdCollection.findOneAndUpdate({cid: cid, did: did}, {$inc: { id: 1} }, {new: true, upsert: true}).exec()
return doc.id
}
app.service.ts 수정
import { Injectable } from '@nestjs/common';
import { User , getNextTs, getNextId} from './db_models';
@Injectable()
export class AppService {
async getHello(name: string): Promise<string> {
const nextId = await getNextId(1, 1)
const nextTs = await getNextTs(1, 1)
console.log("nextTs:" + nextTs)
const user = new User({ id: nextId, name: name, did: 1, ts: nextTs });
user.save().then(() => console.log(name + ' saved'));
return 'Hello! ' + name;
}
}
id도 1씩 증가하고 ts도 1씩 증가한다. 사실 같은 원리다.