반응형

DB와 연결하고 포스트맨으로 API 테스트를 하던 중, 회원 등록 요청에서 아래와 같은 오류가 오류가 발생했다.

ValidationError: User validation failed: password: Path `password` is required., email: Path `email` is required.

↑ 오류 메시지

같이 확인해볼 코드는 register API, userSchema 코드와 포스트맨에서 보낸 request body(req.body), response body 부분이다.

// 회원 등록
app.post('/api/user/register', (req, res) => {
    try {
        const user = new User(req.body);
        console.log(user);
        const userData = user.save();
        return res.status(200).json({ success: true, user:userData});
    } catch {
        return res.status(400).json({ success: false, err});
    }
})

↑ register API (오류 발생)

const userSchema = mongoose.Schema({ // schema 필드 정의
    name: {
        type: String,
        maxLength: 50
    },
    email: {
        type: String,
        required: true,
        unique: true,
        lowercase: true,
        trim: true // 앞뒤 공백 제거
    },
    password: {
        type: String,
        required: true,
        trim: true,
        minLength: 5
    },
    role: {
        type: Number,
        default: 0
    }
})

↑ userSchema

{
    "name": "amanda",
    "email": "amanda@gmail.com",
    "password": "1234567"
}

↑ Postman - request body(req.body)

{
    "success": true,
    "user": {}
}

↑ Postman - response body

🚧 원인 분석

userSchema에서 email, password를 required: true를 설정하고 Postman에서 email, password의 값을 포함해 요청을 보냈음에도, Mongoose는 이 필드가 없다고 판단하며 validation 오류를 발생시킨 것이다.

Express는 기본적으로 요청 본문을 자동으로 파싱하지 않기 때문에, JSON 형식의 요청을 받을 경우 body-parser과 같은 미들웨어를 추가해줘야 req.body가 제대로 채워진다. 그래서 내가 작성한 코드로는 req.body가 undefined 였고, new User(req.body)에 들어가는 값도 비어 있으며, email과 password가 누락된 채 validation에 걸리게 된 것이다.

🛠️ 해결 과정

1. body-parser 미들웨어 추가

body-parser 모듈을 설치한다.

npm install body-parser

이후 코드 상단에 아래와 같이 등록한다.

const bodyParser = require('body-parser'); // HTTP 요청 본문(body) 파싱하는 미들웨어 불러오기

app.use(bodyParser.urlencoded( { extended: true })); // application/x-www-form-urlencoded
app.use(bodyParser.json()); // application/json

↑ register API

여기서 사용한 extended 옵션은 false가 기본이고, false를 설정할 경우 Node.js 기본 모듈인 querystring을 사용해 단순 구조만 파싱한다. true로 설정하면 qs 라이브러리를 사용해서 중첩된 객체나 배열 등 복잡한 구조도 파싱할 수 있다.

여기까지 설정하면 JSON 데이터를 정상적으로 받을 수 있게 된다.

2. async/await 적용

body-parser를 추가해도 여전히 Postman 응답에서 "user": {}처럼 빈 객체가 보였다. Mongoose의 user.save()가 비동기 함수인데 await를 붙이지 않아 저장이 완료되기 전에 응답을 보냈기 때문이다.

// 회원 등록
const bodyParser = require('body-parser'); // HTTP 요청 본문(body) 파싱하는 미들웨어 불러오기

app.use(bodyParser.urlencoded( { extended: true })); // application/x-www-form-urlencoded
app.use(bodyParser.json()); // application/json

app.post('/api/user/register', async (req, res) => {
    try {
        const user = new User(req.body);
        console.log(user);
        const userData = await user.save();
        return res.status(200).json({ success: true, user:userData});
    } catch {
        return res.status(400).json({ success: false, err});
    }
})

↑ register API

Mongoose의 save()는 Promise를 반환하며, 내부적으로 저장 전에 validation을 자동으로 실행한다.

await을 사용하지 않으면 저장이 완료되기도 전에 응답을 보내는 문제가 생길 수 있다.
이 경우 user.save()는 아직 실행 중인 Promise이므로, 응답에서 user: {}처럼 보일 수 있다.
따라서 await을 사용해 저장이 완료된 후 응답을 보내야 한다.

🧠 What I Learned

Express는 요청 본문을 파싱해주는 미들웨어가 필요하다. body-parser.json()을 등록하지 않으면 JSON 형식으로 보낸 데이터도 undefined 처리되어 validation 오류가 발생할 수 있다. user.save()는 비동기 함수이므로, 저장 완료를 보장하려면 await를 사용한다.

반응형
반응형

express를 사용하려면 Node.js가 설치되어 있어야 한다. npm 5.0+ 버전에서는 npm 설치를 하면 package.json 파일의 dependencies에 모듈을 자동으로 추가한다. 이전 버전에서는 npm install (module) --save 옵션을 명시적으로 지정해야 dependencies에 모듈이 자동으로 추가된다.

node -v // 노드 설치 확인

mkdir react-blog // 폴더 생성

npm init // package.json 파일 생성

npm install express
npm install nodemon --save-dev

package.json 파일은 Node.js 프로젝트에 필요한 것을 담고 있으며, 패키지 설치, 빌드, 실행 등에서 중요한 역할을 한다.

Express.js는 웹 프레임워크이다. 특징을 살펴보자.

  • 웹이나 모바일 앱을 만들 때 필요한 기본 기능을 안정적으로 제공한다.
  • HTTP 메서드를 다루는 다양한 유틸리티 메서드가 있어서 API를 쉽고 빠르게 만들 수 있다.
  • 미들웨어를 조합해서 인증, 로깅, 요청 데이터 파싱 등 기능을 손쉽게 추가할 수 있다.
  • Node.js 위에서 작동해서 네이티브 기능을 가리지 않고 그대로 사용할 수 있다. (오버헤드가 적음)
  • 미들웨어
    • Express의 핵심은 최소한의 기능만 갖추고, 미들웨어로 확장하는 방식이다.
    • 미들웨어는 요청과 응답 사이에 필요한 작업을 하는 함수인데, 자유롭게 조합이 가능하다.

🔍 express 기본 코드

const express = require('express');
const app = express();
const port = 5000;

// GET method route
app.get('/', (req, res) => {
    res.send('GET request to the homepage')
});

// POST method route
app.post('/', (req, res) => {
    res.send('POST request to the homepage')
});

app.listen(port, () => {
    console.log(`Server is running on ${port}`) // `(backtick): ES6 표준 문법, 변수 작성 시 사용
});
반응형
반응형

🤔 require()에 대해 알아보자

require()는 Node.js에서 외부 모듈(라이브러리), 로컬 파일, JSON 데이터 등을 불러올 때 사용하는 함수이다. Node.js는 기본적으로 CommonJS 모듈 시스템을 따르며, require()는 해당 방식의 대표적인 모듈 호출 방법이다.

 

🔍 코드 분석

index.js의 코드를 살펴보자.

const express = require('express'); // Express 모듈 사용
const app = express(); // app이라는 새로운 Express 앱 만듦

const mongoose = require('mongoose'); // Mongoose ODM 모듈 불러오기
const bodyParser = require('body-parser'); // HTTP 요청 본문(body) 파싱하는 미들웨어 불러오기
const cookieParser = require('cookie-parser'); // 쿠키를 읽고 파싱하는 미들웨어 불러오기

이 코드에서 사용하는 모든 모듈은 node_modules 폴더에 설치되어 있어야 하며, require()는 해당 경로부터 모듈을 탐색한다.

require()는 Node.js가 내부적으로 node_modules 폴더부터 탐색한다.
예: node_modules/express/index.js 등

📁 Node.js가 모듈을 찾는 순서

예를 들어 require('express')를 실행하면 Node.js는 다음 순서로 파일을 찾는다.

  1. node_modules/express/index.js
  2. node_modules/express/package.json → main 항목 확인
  3. node_modules/express/index.json or .node (특수 바이너리)
  4. 못 찾으면 에러 발생 (MODULE_NOT_FOUND)

 

🤔 import 방식에 대해 알아보자

import는 ES6에서 도입된 ESM 시스템의 문법이다. Node.js는 원래 CommonJS 기반이지만, 현재는 ESM도 공식적으로 지원하고 있다.

🔍 코드 분석

ESM 방식은 정적으로 모듈을 불러오며, 모듈 구조를 컴파일 시점에 분석할 수 있다는 장점이 있다. 브라우저와 호환성이 좋아, TypeScript, React, Next.js 등에서 주로 사용한다.

import express from 'express';

Node.js에서 ESM을 사용하려면 다음 중 하나의 설정이 필요하다.

  1. package.json"type": "module" 설정
  2. 파일 확장자를 .mjs로 변경

 

🧠 What I Learned

Node.js에서 처음으로 require()를 사용하면서 단순히 모듈을 불러오는 방식이라고만 생각하며 import와는 어떤 차이점이 있는지 궁금했다.
이번 정리를 통해 Node.js가 기본적으로 CommonJS를 기반으로 작동한다는 점, 그리고 ES6 이후 등장한 import 방식은 정적 로딩을 기반으로 하며 별도의 설정이 필요하다는 점을 알았다.

반응형
반응형

개발을 하다 보면 이미 작성한 커밋 메시지를 바꿔야 할 때가 있다. 가장 최근 메시지를 수정할 것인지, 과거 여러 이력을 포함하여 복수 커밋을 수정, 삭제 등 수정할 것인지 경우에 따라 커밋 메시지를 수정하는 법에 대해 알아보자.

가장 최근 커밋 수정, 과거 커밋 수정 2가지 경우에 대해 알아보자.

 

🧩 가장 최근 커밋 메시지 수정하기 (amend)

# 최근 커밋 수정하기 (커밋 메시지, 내용 변경)
git commit --amend

# 이미 원격 저장소에 푸시한 커밋을 강제로 업데이트하기
git push --force

# 수정할 커밋을 삭제하고 이전 상태로 완전히 되돌아가기
git reset --hard HEAD~1

# 원격 저장소 최신 상태 가져오기 (리셋 후 동기화)
git pull

가장 최근(commit HEAD) 하나의 커밋만 수정할 때 사용한다. 메시지뿐만 아니라 스테이징 된 파일 추가/삭제 모두 가능하다.
보통 방금 커밋한 내용을 바로 수정하거나 보완할 때 쉽고 간단하게 메시지를 수정할 수 있다.

이미 원격 저장소에 푸시한 경우, 수정한 커밋 메시지를 원격 저장소에 반영하려면 강제로 푸시해야 한다.
⚠️ 강제 푸시는 공동 작업자와의 충돌 위험이 있어서 반드시 협의 후 진행해야 한다.

커밋 메시지 수정 후 되돌리고 싶다면,
가장 최근 커밋 메시지를 바꾼 후, 원래 상태로 돌아가고 싶을 때 git reset을 실행한 후 원격저장소의 최신 상태 동기화를 실행(pull)한다.

HEAD~1은 바로 이전 커밋을 가리키며, --hard 옵션은 커밋 + 스테이징 영역 + 작업 디렉토리까지 모든 변경사항을 제거한다. 즉, 파일의 변경사항이 함께 삭제되는 것이다.
--soft 옵션은 커밋만 되돌리는 것으로 스테이징 영역, 작업 디렉토리는 유지된다. 커밋 내역을 확실하게 제거해야 하는 경우가 아니라면 soft 옵션을 사용하는 것을 권장한다.

 

🧩 과거 여러 커밋 메시지 수정하기 (rebase)

# 최근 커밋 대상으로 리베이스 작업 시작 (수정하고자 하는 커밋 수에 맞게 숫자를 조절)
# HEAD(최신 커밋) 기준으로 이전 2개 커밋까지 포함하여 인터렉티브 리베이스 시작
git rebase -i HEAD~2

# 초기 커밋(최초로 작성)을 포함하여 리베이스 작업 시작
git rebase -i --root

# 리베이스 중단 (취소)
git rebase --abort

# 리베이스 계속하기 (충돌 해결 후)
git rebase --continue

# 리베이스 완료 후 원격 강제 푸시
git push --force

# 현재 브랜치의 총 커밋 개수 확인
git rev-list --count HEAD

# 커밋 로그 간략히 확인 (한 줄로 축약된 커밋 히스토리를 최신순으로 보여줌)
git log -oneline

가장 최신 커밋이 아닌 과거의 커밋 메시지를 수정하기 위해 최근 2개의 커밋을 대상으로 인터렉티브 리베이스를 시작한다. 숫자 2는 최근으로부터 몇 번째 커밋을 수정할 건지에 따라 숫자를 조절할 수 있다.

 

📝 커밋 메시지 수정 과정 (Vim 기준)

1. `git rebase -i HEAD~2`명령어를 실행하면 텍스트 편집기가 열리고 커밋 목록이 pick으로 표시된다.

2. 수정하고자 하는 커밋의 pick을 reword로 변경한다.

3. ESC → :wq → Enter 를 눌러 저장한다.

4. reword로 지정한 커밋 메시지 편집창이 열리면, 원하는 메시지로 수정한 후, 다시 ESC → :wq → Enter로 저장/편집기를 종료한다.

 

🛠️ pick 외에 사용할 수 있는 명령어

pick - 해당 커밋을 그대로 사용
reword - 커밋 내용은 유지하고 메시지만 수정
edit - 해당 커밋 시점에서 멈추고 내용까지 수정 가능
squash - 이전 커밋과 합쳐서 하나의 커밋으로 만듦(메시지 편집도 가능; 어떤 커밋 메시지를 최종적으로 남길지)
fixup - 이전 커밋과 자동으로 합치되, 메시지는 유지 X
drop - 해당 커밋을 삭제

 

🧠 What I Learned

몇 번째 커밋을 수정하느냐에 따라, amend, rebase 중 어떤 명령어를 사용할지 알게 되었다. 
메시지 편집창이 따로 열리는 건지 모르고 pick → reword 수정 후 바로 커밋 메시지 부분을 고쳐서 뒤로 넘기니 수정이 되지 않았다. 2번째로 열리는 곳에서 커밋 메시지를 수정해야 수정이 반영된다.

 

 

반응형
반응형

클론 코딩을 진행하던 중, GitHub에 (1) 알람이 떴다. 확인해보니 "Secret scanning (1)"이라는 보안 경고가 발생했다는 알람이었다. 이는 MongoDB Atlas의 Database URI가 코드에 노출되었음을 감지한 것으로, 민감 정보 유출 위험이 있다는 내용이었다.

MongoDB Atlas 데이터베이스 URI 노출 시 GitHub에서 제안하는 대응 절차를 따라가며 실제로 문제를 해결한 과정을 알아보자.

# Remediation steps

Follow the steps below before you close this alert.

1. Rotate the secret if it's in use to prevent breaking workflows.
2. Revoke this MongoDB Atlas Database URI with credentials through MongoDB to prevent unauthorized access. [Learn more about MongoDB tokens.](https://gh.io/mongodb_atlas_db_uri_with_credentials)
3. Check security logs for potential breaches.
4. Close the alert as revoked.


1. Rotate the secret if it's in use to prevent breaking workflows.
- 의미: 기존 URI가 코드나 배포 환경에 쓰이고 있다면, 단순히 삭제하지 말고 새로운 URI로 먼저 교체해야 한다.
- 조치 방법
  1) MongoDB Atlas에 로그인
  2) 새 사용자 생성 또는 기존 사용자 비밀번호 재설정 (비밀번호 기반 인증일 경우)
  3) 기존 프로젝트 또는 애플리케이션 설정에서 새 URI로 환경변수 혹은 시크릿 값 업데이트 (🧩 dotenv 적용)

2. Revoke this MongoDB Atlas Database URI with credentials through MongoDB to prevent unauthorized access.
- 의미: 외부에 노출된 기존 자격증명은 더 이상 유효하지 않도록 완전히 무력화해야 한다.
- 조치 방법
  1) MongoDB Atlas → Project → Database Access
  2) 노출된 URI에서 사용된 사용자 계정 확인
  3) 해당 사용자 계정 삭제 또는 비밀번호 변경
      - 완전히 사용하지 않을 거면 삭제
      - 다른 데서 여전히 사용중이라면 비밀번호만 재설정

3. Check security logs for potential breaches.
- 의미: 노출된 URI로 접속 시도나 이상한 동작이 있었는지 확인해야 한다.
- 조치 방법
  1) MongoDB Atlas → Project → Activity Feed 또는 Cluster → Metrics → Connection Logs
  2) 최근 IP 주소, 위치, 접속 시간 확인
  3) 의심스러운 접속(평소와 다른 국가, 갑작스러운 대량 쿼리 등) 식별
  4) 필요한 경우 침해 가능성에 따라 데이터 백업, 감사 진행

4. Close the alert as revoked.
- 의미: GitHub Security Alert이나 기타 시스템에서 이번 사고에 대한 대응이 완료되었음을 명시한다.
- 조치 방법
  1) GitHub Repository → Security → Secret scanning alerts
  2) 해당 MongoDB URI 경고 클릭
  3) "Mark as revoked" 또는 "Close as revoked" 선택

이후 index.js에서 URI를 .env 파일로 분리하여 환경변수로 관리하도록 수정하고, 노출된 값을 코드에서 제거한 후 커밋했다.

하지만 이미 이전 커밋에 포함되어 노출되었던 URI는 여전히 GitHub의 Secret scanning 페이지에 기록되어 있었다.  
Git의 히스토리 자체를 수정하지 않는 이상 이전 커밋의 흔적은 그대로 남기 때문에, 원격 저장소를 삭제하고 새로 생성한 후 클론하여 재설정했다.

이번에는 .env 파일을 만들어보자.

🧩 dotenv로 환경변수 관리하기

URI가 코드에 하드코딩되어 있으면 보안상 위험할 뿐만 아니라, 협업 중에도 민감 정보가 노출될 수 있다. 이를 해결하기 위해 Node.js에서는 보통 .env 파일에 환경변수를 정의하고 dotenv 패키지를 사용해 이를 불러온다.

npmjs.com 의 Install과 Usage를 적용하여 환경 변수 만드는 방법을 적용했다.

 

npm install 명령어로 dotenv를 설치했다.

# npm
npm install dotenv --save

# yarn
yarn add dotenv

 

프로젝트 루트 경로에 .env 파일을 생성한다.
(🛠️ 루트 경로가 아닌 곳에 .env 파일을 생성해서 DB 연결이 안 됐음)

.env 파일에서 환경변수를 정의하는 방법은 다음과 같다.

# .env
# 변수명=값

PORT=5000
MONGODB_URI=mongodb+srv://<id>:<password>@....

 

 

.env 파일을 GitHub에 올리는 실수를 하지 않도록, .gitignore 파일에 .env 을 추가해준다.

프로젝트 루트에 있는 index.js  첫 줄에 다음 코드를 입력해서 환경변수를 불러온다.

require('dotenv').config();

 

.env로부터 불러온 환경변수를 사용하기 위해서는 `process.env.변수명` 키워드를 사용한다.

process.env.PORT
process.env.MONGO_URI

 

🛠️ .env 파일에 정의한 변수명과 process.env에서 불러온 이름이 달라서 undefined 상태가 되었다. .env 파일의 MONGODB_URI를 MONGO_URI로 수정해서 해결했다.

# .env
MONGODB_URI=mongodb+srv://~

# index.js
const { PORT, MONGO_URI } = process.env;
mongoose.connect(process.env.MONGO_URI)
        .then(() => console.log('MongoDB Connected'))
        .catch(err => console.error(err));


🧠 What I Learned

민감 정보는 절대 코드에 노출하거나 하드코딩하지 않고, 환경 변수로 관리해야 한다. 이번에는 혼자하는 프로젝트라 큰 문제가 되지 않았지만, 여러명이 함께하거나 회사에서 이런 일이 생기기 않도록 주의 또 주의!!

반응형
반응형

옵시디언에 자료가 점점 많이 쌓여서 지난주 폴더 정리를 진행했다.

독서 목록을 추가하기 위해 Book Search 플러그인을 실행하여 도서 목록을 추가하는데 다음과 같은 에러 메세지가 떴다.

Error 내용은 다음과 같다.

Template Error:
Couldn't find user script folder "Scripts"
Check console for more information

폴더를 정리하면서 Scripts 폴더를 삭제했기 때문인가보다.

휴지통을 보니 Scripts 폴더가 있음을 확인해서 원래 있던 경로로 위치를 옮겼다. 옵시디언 문서 및 폴더를 삭제한 곳을 따로 .trash라는 폴더로 지정해두길 잘한듯. 👍

 

반응형

+ Recent posts