반응형

📦 Node 와 npm — 서버 개발의 시작점

🎯 이 글을 끝내면 브라우저에서 http://localhost:9100 을 열면 내가 만든 Node 서버가 hello my-board 를 돌려줍니다.
⏱️ 걸리는 시간: 약 80분 · 선행: 00-환경설정.md, 01-터미널.md

왜 필요한가

지금까지 쓴 JavaScript 는 전부 브라우저 안에서 돌았다. 브라우저는 안전을 위해 JS 가 하드디스크를 읽거나 네트워크 포트를 여는 걸 막아둔다. 그래서 브라우저 JS 로는 서버를 만들 수 없다.

Node 는 JS 엔진(V8)을 브라우저에서 꺼내 컴퓨터 위에 올려놓은 것이다. 껍데기가 바뀌니 할 수 있는 일이 바뀐다. 파일을 읽고, 포트를 열고, DB 에 접속한다.

3장의 NestJS, 2장의 Next.js, 4장의 mysql2 — 전부 Node 위에서 돈다. 그 밑바닥을 오늘 30줄짜리 코드로 직접 만들어 본다. 오늘 만드는 5줄짜리 서버가 3장에서 NestJS 로 바뀌는 것뿐이다.


개념

브라우저 JS vs Node JS

브라우저Node
전역 객체window, documentglobal, process
파일 읽기불가fs 모듈
포트 열기불가http 모듈
DOM있음없음 (document 를 쓰면 에러)
모듈ESM (import)ESM 과 CJS 둘 다
쓰는 곳화면API 서버, 빌드 도구, CLI

Node 와 npm 과 node_modules

[내 프로젝트]
  package.json          ← "이 프로젝트가 뭘 쓰는지" 적은 명세서 (사람이 관리)
  package-lock.json     ← "정확히 어떤 버전을 깔았는지" 기록 (npm 이 관리)
  node_modules/         ← 실제 라이브러리 파일들 (git 에 안 올림)
        ▲
        │ npm install / npm ci 가 채운다
        │
  [npm 저장소 (인터넷)]

package.json 과 package-lock.json 은 git 에 올린다. node_modules/ 는 안 올린다(.gitignore).

node_modules 는 수만 개 파일에 수백 MB 다. 그래서 커밋하지 않고, 필요할 때 package-lock.json 을 보고 똑같이 복원한다. 9장에서 EC2 에 배포할 때 하는 일이 정확히 이것이다.

npm install vs npm ci

npm install (= npm i)npm ci
기준package.jsonpackage-lock.json 만
lock 파일필요하면 수정함수정 안 함. 안 맞으면 에러
node_modules있으면 살려 씀통째로 지우고 새로 설치
속도보통더 빠름
쓰는 곳내 노트북에서 개발할 때CI, 배포 서버(EC2)
기억할 한 줄: 개발은 npm i, 배포는 npm ci.

dependencies vs devDependencies

npm i mysql2              # dependencies    → 운영에서도 필요
npm i -D typescript       # devDependencies → 개발할 때만 필요

dependencies 는 운영에서도 돌아야 하는 것(@nestjs/core, mysql2, axios, next), devDependencies 는 빌드까지만 필요한 것(typescript, eslint, prettier, jest, @types/*)이다.

@types/node 처럼 타입 정의만 주는 패키지는 컴파일 후 사라지니 -D 로 넣는다.

ESM vs CJS 한 줄

CJS(CommonJS)는 require() / module.exports, ESM은 import / export 다. package.json 에 "type": "module" 을 넣거나 확장자를 .mjs 로 하면 ESM 이고, 아니면 CJS 다. NestJS 서버는 보통 CJS 로 빌드하고 Next.js 는 ESM 을 쓴다.

비동기 — 서버 코드는 전부 이거다

DB 조회, S3 업로드, 외부 API 호출 — 전부 "요청하고 답이 올 때까지 기다리는" 일이다. Node 는 기다리는 동안 다른 요청을 처리한다. 그래서 서버 코드에는 async 가 붙어 있다.

[동기]  주문 → 서서 기다림 → 받음 → 다음 손님        (한 명씩)
[비동기] 주문 → 진동벨 받고 자리로 → 다음 손님 주문 받음 → 벨 울리면 픽업

비동기를 표현하는 방식은 콜백(fs.readFile(p, (err, data) => ...)) → Promise(getUser(1).then().catch()) → async/await(const u = await getUser(1)) 순으로 발전했다. 지금은 세 번째만 쓴다.

await 은 "Promise 가 끝날 때까지 이 함수만 멈춰라"는 뜻이다. await 은 async 함수 안에서만 쓸 수 있다.


따라하기

1단계. Node 를 계산기처럼 써보기

node -e "console.log(1 + 1)"
node -e "console.log(process.version, process.platform)"
node -e "console.log(Object.keys(process.env).length + '개의 환경변수')"

-e 는 execute. 파일 없이 한 줄을 바로 실행한다. 4장에서 DB 연결을 빠르게 테스트할 때도 이걸 쓴다.

대화형으로 쓰려면 node 만 치면 된다. 나올 땐 Ctrl + D 또는 .exit.

2단계. 프로젝트 초기화

mkdir -p ~/Desktop/study/my-board/연습-node
cd ~/Desktop/study/my-board/연습-node
npm init -y
cat package.json
{
  "name": "연습-node",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

-y 는 "전부 기본값으로"다. 빼면 이름·버전을 하나씩 물어본다.

3단계. scripts 로 명령에 별명 붙이기

package.json 의 scripts 는 자주 치는 긴 명령에 짧은 별명을 붙이는 곳이다.

npm pkg set scripts.hello="node -e \"console.log('안녕 Node')\""
npm run hello
> 연습-node@1.0.0 hello
> node -e "console.log('안녕 Node')"

안녕 Node

참고 레포의 서버는 이렇게 생겼다.

script실제 명령언제
npm run start:devnest start --watch개발 중 (코드 고치면 자동 재시작)
npm run buildnest build배포 전 TS → JS 컴파일
npm run start:prodnode dist/main운영 (9장에서 PM2 가 이걸 돌린다)
npm run linteslint ... --fix코드 검사

start 와 test 만 npm start 처럼 run 없이 쓸 수 있고, 나머지는 전부 npm run 이름 이다.

4단계. 패키지 설치해 보기

npm i dayjs
npm i -D typescript
ls node_modules | head -5
cat package.json

dependencies 에 dayjs, devDependencies 에 typescript 가 들어갔는지 확인한다.

node -e "const d=require('dayjs'); console.log(d().format('YYYY-MM-DD HH:mm'))"

package-lock.json 도 같이 생겼다. ls -al 로 확인한다.

npm ci 도 체험해 본다.

rm -rf node_modules
npm ci
ls node_modules | wc -l
node_modules 가 통째로 복원된다. 9장에서 EC2 가 하는 일이 정확히 이것이다. 코드는 git 으로 받고, 라이브러리는 npm ci 로 복원한다.

5단계. async/await 복습

cat > async-연습.js <<'EOF'
// 1초 뒤에 값을 주는 가짜 DB 조회
function findUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id === 0) reject(new Error('id 는 1 이상이어야 한다'));
      else resolve({ id, nickname: '보드지기' + id });
    }, 1000);
  });
}

async function main() {
  console.log('1. 시작');

  const user = await findUser(1);              // 여기서 1초 멈춘다
  console.log('2. 조회 결과:', user);

  // 여러 개를 동시에 — 총 1초면 끝난다 (순서대로 하면 3초)
  const users = await Promise.all([findUser(2), findUser(3), findUser(4)]);
  console.log('3. 동시 조회:', users.map((u) => u.nickname).join(', '));

  // 에러는 try/catch 로 잡는다
  try {
    await findUser(0);
  } catch (e) {
    console.log('4. 에러 잡음:', e.message);
  }

  console.log('5. 끝');
}

main();
EOF
node async-연습.js
1. 시작
2. 조회 결과: { id: 1, nickname: '보드지기1' }
3. 동시 조회: 보드지기2, 보드지기3, 보드지기4
4. 에러 잡음: id 는 1 이상이어야 한다
5. 끝

여기서 꼭 챙길 것 세 가지.

  1. await 은 그 함수 안에서만 멈춘다. 서버 전체가 멈추는 게 아니다.
  2. 독립적인 여러 작업은 Promise.all 로 동시에 돌린다. 3초가 1초가 된다.
  3. 비동기 에러는 반드시 try/catch. 안 잡으면 unhandledRejection 으로 프로세스가 죽는다.
비동기 에러를 try/catch 로 잡지 않으면 unhandledRejection 으로 프로세스가 통째로 죽는다. 참고 레포에서도 백그라운드 작업 때문에 서버가 통째로 죽은 사고가 있었다.

6단계. HTTP 서버를 5줄로 띄우기 — 오늘의 핵심

cat > 서버.js <<'EOF'
const http = require('http');
http
  .createServer((req, res) => res.end('hello my-board'))
  .listen(9100, () => console.log('http://localhost:9100 에서 대기 중'));
EOF
node 서버.js
http://localhost:9100 에서 대기 중

브라우저에서 http://localhost:9100 을 연다. hello my-board 가 보인다.
끄려면 터미널에서 Ctrl + C.

방금 무슨 일이 일어났나.

[브라우저]  GET http://localhost:9100/  ──▶  [9100 포트를 듣고 있는 node 프로세스]
                                                      │
                                              (req, res) 콜백 실행
                                                      │
[브라우저]  ◀── "hello my-board" (200 OK) ────────────┘

이게 서버의 전부다. 포트를 열고 → 요청이 오면 → 응답을 돌려준다. 3장의 NestJS 는 이 위에 라우팅·검증·의존성 주입을 얹은 것뿐이다.

7단계. 경로에 따라 다르게 응답하기 (JSON)

명세 5-2절의 GET /posts 흉내를 내본다.

cat > 서버.js <<'EOF'
const http = require('http');

const posts = [
  { id: 1, title: '첫 글', content: '안녕' },
  { id: 2, title: '둘째 글', content: '반갑다' },
];

const server = http.createServer((req, res) => {
  console.log(req.method, req.url);          // 요청 로그

  if (req.method === 'GET' && req.url === '/health') {
    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    return res.end(JSON.stringify({ status: 'ok', uptime: process.uptime() }));
  }

  if (req.method === 'GET' && req.url === '/posts') {
    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
    return res.end(JSON.stringify(posts));
  }

  res.writeHead(404, { 'Content-Type': 'application/json; charset=utf-8' });
  res.end(JSON.stringify({ statusCode: 404, message: 'Not Found' }));
});

server.listen(9100, () => console.log('9100 대기 중'));
EOF
node 서버.js

다른 터미널 탭(Command + T)에서 확인한다.

curl http://localhost:9100/posts
curl -i http://localhost:9100/health
curl -i http://localhost:9100/없는경로

-i 는 응답 헤더까지 보여준다. curl 은 다음 문서에서 제대로 다룬다.

여기서 이미 명세의 규칙 두 개를 지키고 있다. 응답을 { data: ... } 로 감싸지 않고 데이터를 직접 반환했고, 에러는 { statusCode, message } 형태다.

8단계. 코드를 고치면 자동 재시작 — --watch

지금은 코드를 고칠 때마다 Ctrl + C → node 서버.js 를 반복해야 한다. Node 18 부터는 기본 기능이 있다.

node --watch 서버.js

이제 서버.js 를 저장하면 자동으로 재시작된다.

예전부터 쓰던 도구인 nodemon 도 같은 일을 한다. 참고 레포와 우리 프로젝트는 NestJS 가 제공하는 nest start --watch(= npm run start:dev) 를 쓰므로 nodemon 을 따로 깔 일은 없다.

"저장하면 서버가 자동 재시작된다"는 개념만 알면 된다.
npm pkg set scripts.dev="node --watch 서버.js"
npm run dev

✅ 확인 — 이렇게 보이면 성공

터미널 A:

cd ~/Desktop/study/my-board/연습-node
node --watch 서버.js

터미널 B:

curl -s http://localhost:9100/posts
echo
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:9100/없는경로
[{"id":1,"title":"첫 글","content":"안녕"},{"id":2,"title":"둘째 글","content":"반갑다"}]
404

터미널 A 에는 요청 로그가 쌓인다.

GET /posts
GET /%ec%97%86%eb%8a%94%ea%b2%bd%eb%a1%9c

브라우저 주소창에 http://localhost:9100/posts 를 넣으면 JSON 이 그대로 보인다.


🔧 흔한 에러

증상원인해결
Error: listen EADDRINUSE :::9100이전 서버가 안 죽고 9100 을 점유 중lsof -ti :9100 | xargs kill -9 후 재실행
SyntaxError: Cannot use import statement outside a moduleCJS 파일에서 import 를 씀require() 로 바꾸거나 package.json 에 "type": "module" 추가
ReferenceError: require is not definedESM 모드인데 require 를 씀import 로 바꾸거나 "type": "module" 을 제거
SyntaxError: await is only valid in async functionsasync 없는 함수 안에서 await바깥 함수에 async 를 붙인다
UnhandledPromiseRejection 으로 프로세스가 죽음Promise 에러를 안 잡음await 호출을 try/catch 로 감싼다
Cannot find module 'dayjs'설치 안 했거나 다른 폴더에서 실행pwd 확인 후 해당 폴더에서 npm i dayjs
npm ci 가 package-lock.json not found 로 실패lock 파일이 없거나 커밋 안 됨npm install 을 한 번 돌려 lock 을 만들고 커밋
브라우저에 한글이 � 로 깨짐Content-Type 에 charset 이 없음'application/json; charset=utf-8' 로 지정
코드를 고쳤는데 응답이 그대로--watch 없이 실행 중Ctrl + C 후 node --watch 서버.js
npm i 를 sudo 로 실행했더니 이후 계속 권한 에러폴더 소유자가 root 가 됨sudo chown -R $(whoami) ~/.npm node_modules 후 sudo 없이 재시도

참고 레포에서 보기

파일무엇을 볼지
/Users/me/Desktop/plutosaju/plutosaju_server/package.json scripts 의 start:dev(nest start --watch)·build·start:prod(node dist/main) 세 개를 본다. 9장에서 PM2 가 실행하는 게 start:prod 다. dependencies 에 mysql2·@nestjs/platform-fastify·class-validator 가, devDependencies 에 typescript·jest·@types/* 가 나뉘어 있는 것도 확인한다
/Users/me/Desktop/runepluto/runepluto_web/package.json 프론트 쪽. dev/build/start 가 next 명령이고, 포트를 -p 로 지정한다. 우리 web 은 3100 을 쓸 것이다
/Users/me/Desktop/plutosaju/plutosaju_server/src/main.ts 실제 서버의 시작점. 오늘 만든 http.createServer(...).listen(9100) 과 같은 자리다. NestFactory.create → 미들웨어 등록 → listen 순서를 눈으로만 훑어본다. 3장에서 한 줄씩 해부한다
/Users/me/Desktop/runepluto/runepluto_server/package.json dependencies 목록을 우리 명세와 비교해 본다. bcrypt(5장), @aws-sdk/client-s3(7장)이 미리 보인다

📝 과제

  1. 5줄 서버를 직접 띄우고 브라우저로 확인.
    연습-node/서버.js 를 만들어 9100 포트에서 hello my-board 를 응답한다.
    완료 조건: 브라우저 http://localhost:9100 에 문구가 뜨고, Ctrl + C 로 끄면 브라우저가 "연결할 수 없음"을 보여준다.

  2. POST /posts 흉내 내기.
    7단계 서버에 분기를 추가한다. req.method === 'POST' && req.url === '/posts' 일 때 요청 본문을 읽어 posts 배열에 넣고 201 과 함께 새 글을 반환한다.
    힌트: 본문은 조각으로 온다.

    let body = '';
    req.on('data', (c) => (body += c));
    req.on('end', () => { const p = JSON.parse(body); /* ... */ });

    완료 조건: curl -X POST http://localhost:9100/posts -H 'Content-Type: application/json' -d '{"title":"세번째","content":"테스트"}' 가 201 과 새 글을 돌려주고, 이어서 curl http://localhost:9100/posts 에 3개가 보인다.

  3. npm ci 와 npm i 의 차이 체감하기.
    rm -rf node_modules && time npm ci 와 rm -rf node_modules && time npm i 를 각각 실행해 걸린 시간을 비교하고, package-lock.json 을 일부러 지운 뒤 npm ci 를 돌려 어떤 에러가 나는지 확인한다.
    완료 조건: 두 명령의 차이를 학습일지에 두 줄로 적는다. (lock 파일은 npm i 로 복원한다)


🎯 요약 (3줄)

  1. Node 는 브라우저 밖에서 JS 를 돌리는 껍데기다. http.createServer(...).listen(9100) 이면 이미 서버이고, NestJS 는 그 위에 얹힌 도구일 뿐이다.
  2. package.json 은 명세서, package-lock.json 은 버전 고정 기록, node_modules 는 산출물이다. 개발은 npm i, 배포는 npm ci.
  3. 서버 코드는 전부 비동기다. await 은 async 안에서만 쓰고, 독립 작업은 Promise.all 로 묶고, 에러는 반드시 try/catch 로 잡는다.
반응형
반응형

🌿 git 기초 — 첫 커밋부터 비밀키 보호까지

🎯 이 글을 끝내면 study/my-board 에 .gitignore 와 첫 커밋이 만들어지고, GitHub 에 push 되어 있습니다.
⏱️ 걸리는 시간: 약 80분 · 선행: 00-환경설정.md, 01-터미널.md

왜 필요한가

git 은 작업의 세이브 파일이다. 게임에서 보스 앞에 세이브하고 죽으면 되돌아가듯, 코드도 "여기까진 됐다" 지점을 찍어두고 언제든 돌아올 수 있다.

혼자 공부하는데 왜 필요한가? 세 가지 때문이다.

  1. 되돌리기. 4장에서 DB 를 붙이다가 코드를 망가뜨렸을 때, 어제 커밋으로 1초 만에 돌아간다.
  2. 배포. 9장에서 EC2 에 코드를 올리는 방법이 git clone 이다. git 이 없으면 배포 자체가 안 된다.
  3. 비밀값 보호. .env 에 든 DB 비밀번호와 JWT 시크릿이 GitHub 에 올라가면 진짜 사고가 난다. 이걸 막는 게 .gitignore 다.

명세의 완료 조건 12번이 바로 이거다. ".env·비밀키가 git 에 한 번도 올라간 적 없다."


개념

git 의 3개 공간

[작업 폴더]          [스테이지]           [저장소(.git)]
 working tree        staging area          repository
     │                    │                     │
     │  git add ─────────▶│                     │
     │                    │  git commit ───────▶│
     │◀──────────────────────── git checkout ── │
     │                                          │
     └──────────── git push ───────────────────▶ [GitHub]
공간비유확인 명령
작업 폴더작업 중인 책상git status
스테이지사진 찍을 사람들을 모아둔 자리git status (초록색 목록)
저장소앨범 (커밋 = 사진 한 장)git log
원격(GitHub)클라우드 백업 앨범git remote -v

왜 add 와 commit 이 나뉘어 있나? 한 번에 여러 파일을 고쳤어도 "관련 있는 것만 골라서" 한 커밋으로 묶기 위해서다. add 는 고르는 행위, commit 은 저장하는 행위다.

파일의 4가지 상태

?? file.ts = untracked(git 이 모르는 새 파일), M file.ts = modified(내용만 바뀜), M file.ts(앞칸) = staged(add 해서 커밋 대기), 아무것도 안 보이면 committed.

브랜치

브랜치는 "평행 세계"다. 실무에선 기능마다 브랜치를 따지만, 이 스터디는 혼자 하니 main 하나로 충분하다. 브랜치를 나누는 건 12주 이후에 배워도 늦지 않다. 지금은 main 에 계속 커밋하는 게 더 빨리 완주하는 길이다.


따라하기

1단계. 저장소 만들기

mkdir -p ~/Desktop/study/my-board
cd ~/Desktop/study/my-board
git init
git status
Initialized empty Git repository in /Users/me/Desktop/study/my-board/.git/
On branch main

No commits yet

On branch main 이 나와야 한다. master 가 나오면 00-환경설정.md 의 git config --global init.defaultBranch main 을 빠뜨린 것이다. 지금 고치려면:

git branch -M main

2단계. .gitignore 부터 만든다 — 순서가 중요하다

파일을 만들기 전에 .gitignore 를 먼저 만든다. 한 번 커밋된 파일은 나중에 .gitignore 에 넣어도 계속 추적된다. 순서를 바꾸면 고생한다.
cat > .gitignore <<'EOF'
# ── 의존성 ──
node_modules/

# ── 빌드 산출물 ──
dist/
.next/
out/
build/
*.tsbuildinfo

# ── 비밀값 (가장 중요) ──
.env
.env.local
.env.development
.env.production
*.pem
*.key

# 값이 없는 템플릿은 팀이 봐야 하므로 예외로 추적한다
!.env.example

# ── 로그 ──
logs/
*.log
npm-debug.log*

# ── macOS / 에디터 ──
.DS_Store
.idea/
.vscode/*
!.vscode/extensions.json

# ── 테스트 ──
coverage/
EOF
cat .gitignore
패턴왜 제외하나
node_modules/수만 개 파일에 수백 MB. package.json 만 있으면 npm ci 로 언제든 복원된다
dist/ .next/소스에서 다시 만들어지는 결과물. 커밋하면 충돌만 난다
.envDB 비밀번호, JWT 시크릿, AWS 키가 들어 있다. 절대 올리면 안 된다
*.pem *.keyEC2 접속용 비밀키 (6장)
logs/ *.log계속 커지는 로그 파일
.DS_Store맥이 폴더마다 자동으로 만드는 쓰레기 파일
!.env.example! 는 예외다. 값이 빈 템플릿은 팀이 봐야 하니 올린다

.env 는 막고 .env.example 은 올리는 이 조합이 실무 표준이다. 명세 8절의 환경변수 목록이 바로 .env.example 의 내용이 된다.

3단계. 첫 커밋

cat > README.md <<'EOF'
# my-board

풀스택 완주 프로젝트. Next.js(3100) + NestJS/Fastify(9100) + MySQL(my_board).
EOF

git status
git add .gitignore README.md
git status
git commit -m "chore: 프로젝트 초기화 (.gitignore, README)"
git log --oneline
a1b2c3d chore: 프로젝트 초기화 (.gitignore, README)
git add . 로 전부 담을 수도 있지만, 처음엔 파일 이름을 직접 적는 습관을 들이는 게 안전하다. 실수로 .env 를 담는 사고를 막아준다.

4단계. 커밋 메시지 규칙

참고 레포들처럼 앞에 종류를 붙인다. feat:(새 기능) fix:(버그) chore:(설정·잡일) refactor:(정리) docs:(문서) test:(테스트).

git commit -m "feat: posts 목록 API 추가"

한글로 써도 된다. 중요한 건 "무엇을 왜 바꿨는지"가 보이는 것이다. 수정, ㅇㅇ, asdf 같은 메시지는 3주 뒤의 자신을 괴롭힌다.

5단계. 상태 확인 3종 세트

echo "추가 줄" >> README.md

git status              # 뭐가 바뀌었나
git status -sb          # 짧게 (실무에서 이걸 더 많이 쓴다)
git diff                # 아직 add 안 한 변경 내용
git add README.md
git diff --staged       # add 한 변경 내용
git log --oneline --graph --decorate -10
명령보여주는 것
git status파일 단위로 무엇이 바뀌었나
git diff줄 단위로 무엇이 바뀌었나 (add 전)
git diff --staged줄 단위 (add 후, 커밋 직전 최종 점검)
git log --oneline커밋 이력 한 줄씩
git show HEAD마지막 커밋의 전체 변경 내용
커밋하기 전에 git diff --staged 를 보는 습관이 비밀값 유출을 막는 마지막 방어선이다.

6단계. GitHub 원격에 올리기

GitHub 에서 New repository → 이름 my-board → Private 선택 → README·gitignore 는 추가하지 않음(이미 우리가 만들었다) → Create.

그다음 나오는 주소를 쓴다. HTTPS 대신 SSH 방식을 권한다. 매번 비밀번호를 안 물어본다.

# SSH 키가 없으면 한 번만 만든다
ls ~/.ssh/id_ed25519.pub || ssh-keygen -t ed25519 -C "본인이메일@example.com"
cat ~/.ssh/id_ed25519.pub

출력된 ssh-ed25519 AAAA... 전체를 복사해서 GitHub → 우측 상단 프로필 → Settings → SSH and GPG keys → New SSH key 에 붙여 넣는다.

ssh -T git@github.com          # "Hi 아이디! You've successfully authenticated" 가 나오면 성공
git remote add origin git@github.com:본인아이디/my-board.git
git remote -v
git push -u origin main

-u 는 "앞으로 git push 만 쳐도 여기로 보내"라는 뜻이다. 한 번만 붙이면 된다.

7단계. 실수 복구 3가지

git 의 진짜 가치는 여기 있다.

(1) 파일을 망가뜨렸다. 마지막 커밋 상태로 되돌리고 싶다.

echo "망가뜨림" > README.md
git status
git restore README.md          # 최신 문법
# git checkout -- README.md    # 예전 문법. 같은 동작
cat README.md
이건 저장 안 된 변경을 버리는 것이다. 되살릴 수 없다.

(2) 커밋을 했는데 메시지가 틀렸거나 파일을 빠뜨렸다.

git commit --amend -m "chore: 프로젝트 초기화 (.gitignore, README, 포트 메모)"

(3) 커밋을 통째로 취소하고 싶다.

git reset --soft HEAD~1     # 커밋만 취소. 변경 내용은 스테이지에 그대로 (가장 안전)
git reset --mixed HEAD~1    # 커밋+add 취소. 변경 내용은 작업 폴더에 남음 (기본값)
git reset --hard HEAD~1     # 커밋+변경 내용까지 전부 삭제 (위험)
옵션커밋스테이지작업 파일언제
--soft취소유지유지메시지만 다시 쓰고 싶을 때
--mixed취소취소유지다시 나눠서 커밋하고 싶을 때
--hard취소취소삭제정말 다 버릴 때만
--hard 는 습관적으로 쓰지 않는다. 헷갈리면 --soft 를 쓴다. 손실이 없다.

(4) 최후의 보험: reflog. --hard 로 날려도 살릴 수 있다. git 은 모든 HEAD 이동을 기록한다. git reflog 로 돌아갈 지점의 해시를 찾아 git reset --hard <해시>.

8단계. 비밀키를 커밋해 버렸을 때

가장 중요한 절이다. 5장에서 JWT_SECRET, 6장에서 AWS 키를 다루니 언젠가 겪는다.

핵심 원칙: 한 번 올라간 비밀값은 "지우는 것"으로 해결되지 않는다. 반드시 값을 바꿔야 한다. 푸시된 순간 GitHub 의 캐시·포크·크롤러 봇에 남는다. 공개 저장소라면 몇 분 안에 스캔 봇이 가져간다.

상황 A. 아직 커밋 안 했다 (git status 에만 보임)

git restore --staged .env     # 스테이지에서 빼고
echo ".env" >> .gitignore     # .gitignore 에 추가
git status                    # .env 가 목록에서 사라졌는지 확인

상황 B. 커밋은 했지만 push 는 안 했다

git reset --soft HEAD~1       # 커밋 취소
git restore --staged .env     # 스테이지에서 제외
echo ".env" >> .gitignore
git add .gitignore
git commit -m "chore: .env 를 gitignore 에 추가"
git log -p | grep -c "JWT_SECRET"    # 0 이 나와야 한다

상황 C. 이미 push 했다

순서를 지킨다. 1번을 먼저 한다.

  1. 값을 즉시 무효화한다 (가장 먼저).
    • JWT_SECRET → 새 값으로 교체. 기존 토큰은 전부 무효가 된다 (정상)
    • AWS 키 → AWS 콘솔 → IAM → 사용자 → 보안 자격 증명 탭 → 해당 액세스 키 비활성화 후 삭제 → 새 키 발급
    • DB 비밀번호 → RDS 또는 MySQL 에서 변경
  2. 추적에서 제외한다.
    git rm --cached .env          # 파일은 남기고 git 추적만 해제
    echo ".env" >> .gitignore
    git commit -m "chore: .env 추적 제외"
    git push
  3. 이력에서도 지운다 (선택이지만 권장). brew install git-filter-repo 후 git filter-repo --invert-paths --path .env --force → git remote add origin ... → git push --force origin main. 모든 커밋 해시가 다시 만들어지므로, 협업 중이라면 팀 전원이 다시 clone 해야 한다.
  4. 검증한다.
    git log -p | grep -i "JWT_SECRET\|AWS_SECRET\|password" | head
    빈 결과면 끝. 이게 명세 완료 조건 12번이다.

예방책 — 커밋 전에 막기

.git/hooks/pre-commit 에 간단한 검사를 넣어두면 사고 자체를 막는다.

cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
if git diff --cached --name-only | grep -qE '(^|/)\.env$|\.pem$|\.key$'; then
  echo "중단: .env / .pem / .key 가 커밋에 포함되어 있다."
  echo "git restore --staged <파일> 로 제외한 뒤 다시 커밋한다."
  exit 1
fi
EOF
chmod +x .git/hooks/pre-commit

이제 .env 를 실수로 add 해도 커밋이 막힌다.


확인 — 이렇게 보이면 성공

cd ~/Desktop/study/my-board
git add -A && git commit -qm "docs: README 보강"   # 5단계에서 고친 것 먼저 정리
echo "SECRET=1234" > .env        # 일부러 비밀 파일을 만들어 본다
git status -sb
## main...origin/main
.env 가 목록에 안 보이면 성공이다. .gitignore 가 제대로 먹고 있다는 뜻이다. (아직 push 전이라면 ## main 만 나온다.)
git check-ignore -v .env
.gitignore:12:.env	.env

".gitignore 12번째 줄 때문에 무시된다"는 뜻이다. 커밋 이력도 확인한다.

git log --oneline
git remote -v
b2c3d4e docs: README 보강
a1b2c3d chore: 프로젝트 초기화 (.gitignore, README)
origin	git@github.com:본인아이디/my-board.git (fetch)
origin	git@github.com:본인아이디/my-board.git (push)

🔧 흔한 에러

증상원인해결
fatal: not a git repositorygit init 을 안 했거나 다른 폴더에 있음pwd 확인 후 프로젝트 루트에서 git init
Author identity unknownuser.name / user.email 미설정git config --global user.name "홍길동" git config --global user.email "메일"
.gitignore 에 넣었는데 계속 추적됨이미 커밋된 파일은 .gitignore 가 안 먹는다git rm --cached 파일명 으로 추적 해제 후 커밋
Permission denied (publickey)GitHub 에 SSH 공개키를 안 등록함cat ~/.ssh/id_ed25519.pub 을 GitHub Settings → SSH keys 에 등록. ssh -T git@github.com 으로 확인
Updates were rejected ... fetch first원격이 로컬보다 앞서 있음git pull --rebase origin main 후 다시 push
error: src refspec main does not match any커밋이 하나도 없음먼저 git add + git commit 을 한 뒤 push
git commit 을 쳤더니 vim 같은 편집기가 열림-m 을 빼먹음:q! 로 나간 뒤 git commit -m "메시지". 또는 core.editor 를 code --wait 로 설정
--hard 로 작업을 날렸다되돌리기 명령을 잘못 씀git reflog 로 직전 해시를 찾아 git reset --hard <해시>
node_modules 가 커밋되어 push 가 매우 느림.gitignore 없이 git add . 을 함git rm -r --cached node_modules 후 .gitignore 추가하고 커밋

참고 레포에서 보기

파일무엇을 볼지
/Users/me/Desktop/plutosaju/plutosaju_server/.gitignore서버용 .gitignore. node_modules/, dist/, .env, logs/ 가 어떻게 적혀 있는지 본다. 우리가 만든 것과 비교
/Users/me/Desktop/runepluto/runepluto_web/.gitignoreNext.js 용. .env* 로 전부 막고 !.env.example 로 템플릿만 예외 처리한 부분, /.next/ *.tsbuildinfo 제외를 확인한다. 주석으로 왜 예외인지까지 적어둔 게 좋은 습관이다
/Users/me/Desktop/runepluto/runepluto_server/.env.example실제 커밋되어 있는 템플릿. 값이 전부 비어 있고 키 이름과 설명만 있다. 우리 server/.env.example 도 이렇게 만든다 (명세 8절)
/Users/me/Desktop/runepluto/runepluto_server/ddl/CHANGELOG.mdDB 스키마 변경을 날짜순으로 누적 기록한 파일. 커밋 메시지만으로 부족한 이력을 문서로 남기는 방식이다 (4장에서 따라 한다)

📝 과제

  1. my-board 저장소 완성. git init → .gitignore 작성 → README.md 작성 → 첫 커밋 → GitHub private 저장소에 push. 완료 조건: GitHub 웹에서 저장소를 열었을 때 README.md 와 .gitignore 두 파일만 보인다. (주차 플랜 1주차 완료 조건에 해당)
  2. .gitignore 동작을 직접 증명하기. echo "JWT_SECRET=abcd" > .env 로 파일을 만든 뒤 git status 에 안 나오는 것을 확인하고, git check-ignore -v .env 로 몇 번째 줄 규칙 때문인지 출력한다. 완료 조건: git add . 을 실행해도 .env 가 스테이지에 안 담긴다.
  3. 복구 리허설. README.md 를 3번 고쳐 3개의 커밋을 만든다. 그다음 ① git restore 로 저장 안 한 변경 버리기, ② git commit --amend 로 메시지 고치기, ③ git reset --soft HEAD~1 로 커밋 취소 후 재커밋을 각각 해본다. 마지막에 git reflog 를 열어 지금까지의 이동 이력을 읽는다. 완료 조건: 세 가지를 전부 실행했고, git log --oneline 이 의도한 커밋 3개를 보여준다.

🎯 요약 (3줄)

  1. git add(고르기) → git commit(저장) → git push(백업) 세 박자. 커밋 전엔 git diff --staged 로 무엇이 담겼는지 반드시 본다.
  2. .gitignore 는 파일을 만들기 전에 먼저 만든다. node_modules, .next, dist, .env 는 무조건 제외하고 .env.example 만 예외로 올린다.
  3. 비밀키를 올렸다면 지우기보다 값 교체가 먼저다. 실수는 git restore / git commit --amend / git reset --soft 로 대부분 복구되고, 최후엔 git reflog 가 있다.
반응형
반응형

🖥️ 터미널 완전 정복 치트시트 (프론트 개발자를 위한 20개 명령어)

🎯 이 글을 끝내면 터미널만으로 폴더를 만들고 파일을 쓰고 읽고 지우고, 9100 포트를 누가 쓰는지 찾아낼 수 있게 됩니다.
⏱️ 걸리는 시간: 약 70분 · 선행: 개발 환경설정 완료

왜 터미널이 필요한가

터미널은 컴퓨터에게 말로 시키는 창구입니다. 마우스가 "메뉴에서 골라 클릭"이라면, 터미널은 "이거 해"라고 문장으로 말하는 것이에요.

프론트만 하던 사람은 npm run dev 정도만 치고 살았을 겁니다. 그런데 AWS 서버(EC2)에 접속하면 화면이 없습니다. 마우스도 없어요. 검은 화면에 글자뿐입니다. 거기서 파일을 옮기고 프로그램을 띄우고 로그를 봐야 합니다.

터미널은 피할 수 없지만, 실제로 쓰는 명령은 20개도 안 됩니다. 이 글이 바로 그 20개예요.

개념부터 잡기

폴더 구조와 "현재 위치"

터미널에는 항상 "지금 내가 서 있는 폴더"가 있습니다. 이걸 현재 작업 디렉터리(cwd) 라고 불러요.

/                                 ← 루트. 모든 것의 꼭대기
├── Users/
│   └── me/                       ← 홈 디렉터리. 줄여서 ~
│       └── Desktop/
│           └── study/
│               └── my-board/     ← 여기에 서 있다면
│                   ├── web/      ← cd web      (상대 경로)
│                   └── server/   ← cd ./server (같은 뜻)
└── etc/

절대 경로 vs 상대 경로

종류예시뜻
절대 경로/Users/me/Desktop/study/ 부터 시작. 어디서 치든 같은 곳
홈 기준~/Desktop/.../study~ = 내 홈 폴더. 절대 경로와 사실상 같음
상대 경로web/src지금 서 있는 곳 기준
부모 폴더..한 칸 위
현재 폴더.지금 여기

cd ../server 는 "한 칸 위로 갔다가 server 로 들어가"라는 뜻입니다.

명령어 한 줄의 구조

  ls        -al      ~/Desktop
  │          │          │
  명령     옵션      대상(인자)

옵션은 - 하나에 한 글자(-a), -- 두 개에 단어(--help) 형태입니다. -al 은 -a -l 을 붙여 쓴 것이에요.


직접 따라해 보기

1단계. 위치 파악과 이동

pwd                 # 지금 어디 있나 (print working directory)
cd ~                # 홈으로
cd ~/Desktop/study
pwd
cd ..               # 한 칸 위
cd -                # 직전에 있던 곳으로 되돌아가기

cd 만 치고 Enter 해도 홈으로 갑니다.

2단계. 목록 보기

ls                  # 이름만
ls -l               # 자세히 (권한·크기·수정일)
ls -a               # 숨김 파일(.env, .gitignore)까지
ls -al              # 둘 다. 제일 많이 쓴다
ls -lh              # 크기를 1.2K, 3.4M 처럼 읽기 좋게
.env 나 .gitignore 는 ls 만 치면 안 보입니다. 점(.)으로 시작하는 파일은 숨김 처리라서 -a 옵션이 필요해요. 이걸 모르면 나중에 ".env 를 만들었는데 없어졌다"며 헤매게 됩니다.

3단계. 만들기

cd ~/Desktop/study
mkdir 연습                       # 폴더 하나
mkdir -p 연습/a/b/c              # 중간 폴더까지 한 번에 (-p = parents)
cd 연습
touch memo.txt                   # 빈 파일 만들기
ls -al

4단계. 파일에 글 쓰고 읽기

echo "첫 줄이다" > memo.txt      # >  : 덮어쓰기
echo "둘째 줄이다" >> memo.txt   # >> : 이어붙이기
cat memo.txt                     # 전체를 한 번에 출력
첫 줄이다
둘째 줄이다
> 와 >> 의 차이는 반드시 몸에 익혀야 합니다. > 는 기존 내용을 전부 날립니다. 서버 로그 파일에 > 를 잘못 쓰면 로그가 통째로 사라져요.

파일이 길면 cat 은 화면을 넘겨버립니다. 이럴 땐 less 를 쓰세요.

less memo.txt

less 안에서는:

키동작
Space / f다음 화면
b이전 화면
/검색어아래로 검색 (n 으로 다음 결과)
G맨 끝으로
g맨 앞으로
q나가기
q 를 모르면 영영 못 나옵니다. 초보자가 제일 자주 갇히는 곳이에요. "less 에서 못 나가겠다" 싶으면 무조건 q!

5단계. 복사 · 이동 · 삭제

cp memo.txt memo-backup.txt      # 복사
cp -r a a-copy                   # 폴더 복사는 -r (recursive)
mv memo-backup.txt 백업.txt       # 이름 바꾸기
mv 백업.txt a/                    # 옮기기 (대상이 폴더면 이동)
rm a/백업.txt                     # 파일 삭제
rm -r a-copy                     # 폴더 삭제
rm 에는 휴지통이 없습니다. 지우면 끝이에요. 특히 아래 두 개는 절대 치지 마세요.
rm -rf /        # 컴퓨터를 날린다
rm -rf ~        # 홈 폴더를 통째로 날린다

안전하게 쓰려면 rm -i (하나씩 물어봄) 를 쓰거나, 지우기 전에 ls 로 먼저 확인하는 습관을 들이세요.

6단계. Tab 자동완성과 Ctrl 단축키

이 두 개를 안 쓰면 터미널 속도가 5배 느려집니다. 반드시 익히세요.
cd ~/Desk        # 여기서 Tab → cd ~/Desktop/ 으로 완성됨
cat me           # Tab → cat memo.txt

Tab 을 눌렀는데 아무 일도 없으면 후보가 여러 개라는 뜻입니다. Tab 을 한 번 더 누르면 후보 목록이 떠요.

단축키동작언제 쓰나
Tab자동완성항상
Ctrl + C실행 중인 프로그램 강제 종료npm run dev 를 끌 때. 가장 많이 씀
Ctrl + D입력 끝 / 셸 종료node REPL 에서 나올 때
Ctrl + L화면 지우기 (clear 와 같음)지저분할 때
Ctrl + A / Ctrl + E줄 맨 앞 / 맨 끝으로긴 명령 고칠 때
Ctrl + U커서 앞을 전부 지우기잘못 친 줄 버릴 때
↑ / ↓이전 명령 불러오기항상
Ctrl + R예전 명령 검색ssh 처럼 긴 명령 다시 칠 때

7단계. 파이프 | 와 리다이렉트 >

파이프는 앞 명령의 출력을 뒤 명령의 입력으로 넘기는 관입니다.

[ls -al]  ──출력──▶  |  ──입력──▶  [grep .env]  ──▶ 화면
ls -al | grep env          # 목록 중 env 가 들어간 줄만
cat memo.txt | wc -l       # 줄 수 세기
ls -al > 목록.txt           # 화면 대신 파일로 저장
ls -al | grep txt > 결과.txt

wc -l 은 줄 수, wc -c 는 글자 수를 셉니다.

8단계. grep — 내용 검색

grep 은 "이 글자가 들어간 줄을 찾아줘"입니다. 서버 로그에서 에러를 찾을 때 매일 써요.

grep "둘째" memo.txt                     # 파일 안에서 찾기
grep -n "둘째" memo.txt                  # 줄 번호까지 (-n)
grep -i "DULJJAE" memo.txt               # 대소문자 무시 (-i)
grep -r "PORT" ~/Desktop/.../01-basics   # 폴더 전체 재귀 (-r)

실무에서는 이렇게 씁니다.

pm2 logs --lines 200 | grep -i error     # 최근 로그에서 에러만

9단계. 프로세스와 포트 — 서버 공부 내내 쓴다

우리 서버는 9100 포트, 프론트는 3100 포트를 씁니다. 서버 공부를 시작하면 이런 에러를 반드시 만나요.

Error: listen EADDRINUSE: address already in use :::9100

"9100 을 이미 누가 쓰고 있다"는 뜻입니다. 범인을 찾아서 종료해야 해요.

lsof -i :9100          # 9100 포트를 쓰는 프로세스 찾기
COMMAND   PID     USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    54321 me         24u  IPv6 0x1a2b3      0t0  TCP *:9100 (LISTEN)

PID(프로세스 번호) 54321 이 범인입니다.

kill 54321             # 정중하게 종료 요청 (권장)
kill -9 54321          # 말을 안 들으면 강제 종료

한 줄로 끝내려면:

lsof -ti :9100 | xargs kill -9

-t 는 PID 만 출력하는 옵션이고, xargs 는 그 값을 뒤 명령의 인자로 넘깁니다.

현재 돌고 있는 프로세스를 보는 명령도 알아두세요.

ps aux | grep node                  # node 로 돌고 있는 것들
ps aux | grep -v grep | grep node   # grep 자기 자신은 빼고
명령한 줄 요약
lsof -i :91009100 포트를 점유한 프로세스 찾기
ps aux | grep node실행 중인 node 프로세스 목록
kill PID정상 종료 요청
kill -9 PID강제 종료 (최후의 수단)
Ctrl + C지금 터미널에서 돌고 있는 것을 끄기

10단계. ssh 맛보기

ssh 는 다른 컴퓨터의 터미널을 내 터미널에서 쓰는 것입니다. AWS EC2 에 접속할 땐 이렇게 생겼어요.

ssh -i ~/.ssh/my-board-key.pem ubuntu@3.36.12.34
조각뜻
-i ~/.ssh/....pem열쇠 파일(비밀키). AWS 가 EC2 만들 때 준다
ubuntu접속할 계정 이름 (Ubuntu 의 기본 계정)
3.36.12.34서버의 공인 IP

접속하면 프롬프트가 me@맥북 % 에서 ubuntu@ip-172-31-x-x:~$ 로 바뀝니다. 이때부터 치는 명령은 전부 AWS 컴퓨터에서 실행돼요. 나올 때는 exit.

지금은 "내 맥에서 배운 명령이 저기서도 똑같이 먹힌다"는 것만 알면 됩니다. 그래서 이 글이 AWS 실습의 준비물이에요.

✅ 확인 — 이렇게 나오면 성공

아래를 순서대로 치고 마지막 출력이 일치하면 성공입니다.

cd ~/Desktop/study
mkdir -p 연습/로그
cd 연습
printf 'INFO 시작\nERROR DB 연결 실패\nINFO 종료\n' > 로그/app.log
grep -n ERROR 로그/app.log
cat 로그/app.log | wc -l
2:ERROR DB 연결 실패
       3

포트 확인도 해봅니다. 아무것도 안 띄운 상태라면 결과가 비어 있는 게 정상이에요.

lsof -i :9100
echo "종료코드: $?"
종료코드: 1

($? 는 직전 명령의 종료 코드입니다. 0 이면 성공, 0 이 아니면 실패/결과 없음.)


🔧 흔한 에러 해결

증상원인해결
zsh: no such file or directory: web지금 폴더에 web 이 없음pwd 로 위치 확인 → ls 로 이름 확인 → Tab 자동완성
less 를 열었는데 못 나감종료 키를 모름q 를 누른다
rm 으로 지웠는데 복구하고 싶다rm 에는 휴지통이 없음복구 불가. 앞으로 ls 로 먼저 확인하거나 rm -i 사용
EADDRINUSE ... :::9100이전 서버가 안 죽고 남음lsof -ti :9100 | xargs kill -9 후 재실행
permission denied: ./deploy.sh실행 권한이 없음chmod +x deploy.sh 후 재실행
ls 했는데 .env 가 안 보임점으로 시작하면 숨김ls -a 또는 ls -al
한글 폴더에서 Tab 이 이상하게 완성됨맥의 유니코드 정규화따옴표로 감싼다: cd "연습"
> 로 썼더니 기존 내용이 날아감> 는 덮어쓰기이어붙이려면 >>. 복구는 불가
npm run dev 터미널에서 다른 명령이 안 먹힘그 터미널은 서버가 점유 중새 탭(Command + T) 또는 Ctrl + C 로 서버 끄기

📝 과제

과제 1. 연습 문제 10개 (전부 터미널로)

~/Desktop/study/연습 폴더 안에서 풉니다. 답은 직접 명령을 만들어 내세요.

  1. 지금 서 있는 폴더의 절대 경로를 출력하라.
  2. 연습 안에 drill/a/b 폴더를 한 줄 명령으로 만들어라.
  3. drill/a/b 안에 hello.txt 를 만들고 hello world 를 써 넣어라 (touch 없이 한 줄로).
  4. hello.txt 에 second line 을 기존 내용을 지우지 않고 추가하라.
  5. hello.txt 를 drill/a 로 복사하고, 원본은 greeting.txt 로 이름을 바꿔라.
  6. 연습 아래에서 line 이 들어간 파일을 줄 번호와 함께 전부 찾아라 (재귀).
  7. 연습 의 파일 목록을 목록.txt 로 저장하되, 숨김 파일도 포함하라.
  8. 목록.txt 가 몇 줄인지 파이프를 써서 세어라.
  9. 지금 내 맥에서 3100 포트를 쓰는 프로세스가 있는지 확인하고, 있으면 PID 를 찾아 종료하라.
  10. drill 폴더를 통째로 삭제하라. 단, 삭제 전에 ls -R drill 로 먼저 확인하라.
완료 조건: 10개를 전부 직접 친 명령이 ~/.zsh_history 에 남습니다. 확인은 history | tail -30.

과제 2. 포트 점유 상황을 일부러 만들고 해결하기

터미널 A 에서 아래를 실행해 9100 을 점유시킵니다.

node -e "require('http').createServer((q,s)=>s.end('hi')).listen(9100, ()=>console.log('9100 점유중'))"

터미널 B(Command + T)에서 같은 명령을 다시 실행해 EADDRINUSE 를 직접 봅니다. 그다음 터미널 B 에서 lsof -i :9100 으로 PID 를 찾아 kill 로 종료하세요.

완료 조건: 터미널 A 의 프로세스가 죽고, 터미널 B 에서 같은 명령이 이제 성공한다.

과제 3. 나만의 alias 3개 만들기

~/.zshrc 에 자주 쓸 단축어 3개를 추가합니다. 예: alias p9='lsof -i :9100'.

완료 조건: source ~/.zshrc 후 새로 만든 단축어 3개가 전부 동작한다.

🎯 요약 (3줄)

  1. pwd(어디) → ls -al(뭐가 있나) → cd(이동) 세 개가 터미널의 90%입니다. 나머지는 Tab 과 ↑ 로 칩니다.
  2. | 는 앞 출력을 뒤로 넘기는 관, > 는 덮어쓰기, >> 는 이어쓰기입니다. grep 과 조합하면 로그에서 에러만 뽑아냅니다.
  3. 서버 공부 중 EADDRINUSE 가 뜨면 lsof -i :9100 으로 범인을 찾고 kill 로 끝냅니다. 이 한 세트를 계속 씁니다.
반응형
반응형

✅ Widget이란?

  1. 독립적으로 실행되는 작은 프로그램
  2. 주로 바탕화면, 앱 화면 등에서 날씨·뉴스·생활정보 등을 보여줌
  3. 그래픽이나 데이터 요소를 처리하는 함수를 가짐

👉 일반적으로 말하는 위젯은 작은 프로그램이지만,
Flutter에서의 위젯은 더 넓은 개념을 가집니다.


✅ Flutter의 위젯

  1. UI를 만들고 구성하는 모든 기본 단위 요소
  2. 보이지 않는 레이아웃 요소까지도 위젯
  3. 즉, Flutter의 모든 것은 위젯

✅ Flutter 위젯의 종류

  • Stateless Widget
    • 상태가 없는 정적인 위젯
    • 이전 상호작용의 값을 저장하지 않음
    • 예: 텍스트, 아이콘, 이미지
  • Stateful Widget
    • 상태(state)를 추적하고 보존하는 위젯
    • 사용자의 입력이나 데이터 변화에 따라 모양 변화
    • 예: 버튼 클릭, 텍스트 입력, 데이터 로딩 상태
  • Inherited Widget
    • 위젯 트리 전체에 데이터를 전달하는 특수한 위젯
    • 주로 상태 관리에 활용

✅ Stateless 위젯 특징

  1. 화면에 존재만 할 뿐 변화 없음
  2. 실시간 데이터 저장 불가
  3. 변화(모양·상태)를 유발하는 value 값 없음

✅ Stateful 위젯 특징

  1. 사용자의 상호작용(클릭, 입력 등)에 따라 모양이 변함
  2. 데이터를 받으면 UI가 다시 그려짐

✅ Flutter 위젯 트리 (Widget Tree)

위젯들은 트리(tree) 구조로 정리됩니다.
부모 위젯(Parent) 안에 자식 위젯(Child)을 포함할 수 있으며, 이를 Widget Tree라고 합니다.

예시 구조:
MyApp
└─ MaterialApp
└─ MyHomePage
└─ Scaffold
├─ AppBar
└─ Center
└─ Column
├─ Text
├─ Image
└─ Button


✅ 간단 예시 코드

1) Stateless Widget

import 'package:flutter/material.dart';

class MyStatelessWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const Text(
      '안녕하세요! 저는 Stateless Widget 입니다.',
      style: TextStyle(fontSize: 20),
    );
  }
}



import 'package:flutter/material.dart';

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('버튼 클릭 횟수: $_count', style: const TextStyle(fontSize: 20)),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('클릭'),
        ),
      ],
    );
  }
}

 

📌 정리 (Summary)
Flutter의 모든 것은 위젯이다.
변화가 없는 UI → Stateless Widget
모양·상태가 바뀌는 UI → Stateful Widget
위젯은 항상 트리 구조로 구성된다.

반응형

'Flutter > Flutter' 카테고리의 다른 글

[ Flutter ] Container 위젯  (0) 2023.09.21
반응형

업비트에서 바이낸스( 국내에서 해외로 코인 전송 )로 출금하는 방법입니다.

 

업비트 로그인 / 바이낸스 로그인 이후 내용입니다.

 

1. 검색창에 원하는 코인을 입력하고 클릭합니다.

 

 

2. 시장가를 클릭하고 주문총액을 입력한 후 매수를 클릭합니다.

 

 

3. 입출금을 클릭한 후, 매수한 코인 이름을 검색한 다음 출금을 누르고 출금수량을 최대로 한다음 아래 출금신청을 클릭합니다.

 

 

4. 받는사람 주소를 입력하는 창이 열리게 됩니다. ( 현재 창은 그대로 두고, 바이낸스로 넘어갑니다. )

 

 

5. 우측 상단에 Deposit을 클릭합니다.

 

 

6. 하단에 암호화폐 입금 ( Deposit Crypto )를 클릭합니다.

 

 

 

7. 매수한 코인을 선택하고 네트워크도 선택해줍니다. ( 트론의 메인 네트워크는 TRC20입니다. ) 그리고 Address를 복사합니다.

 

 

8. 다시 업비트로 돌아와서 복사한 주소를 붙여넣고 아래 출금신청을 클릭합니다. 출금 전 잠깐이라는 내용이 나오는데, 아니요를 클릭하시면 됩니다.

 

 

9. 카카오톡 인증을 진행합니다.

 

 

10. 출금을 완료하면 출금 진행중이라는 문구가 뜨고, 1 ~ 2분 정도 기다리면 출금 완료가 됩니다.

 

 

11. 우측 상단에 지갑모양을 클릭하고 Spot을 클릭하면 매수한 코인이 보이며, Convert를 클릭해서 매수한 코인을 USDT로 옮기면 작업은 완료 됩니다.

 

 

전송 네트워크만 잘 확인하시면, 다른 코인으로도 방법은 같습니다. 

 

부 - 자 되세요~

반응형
반응형

Java JDK를 설치하는 방법입니다.

 

java JDK 다운로드

 

위 오라클 사이트에서 DMG로 받았습니다.

 

 

받은 dmg파일을 열어 .pkg파일을 실행해서 설치를 해줍니다.

 

설치 확인하기

$ java -version

 

 

 

설치한 java JDK 확인하기

$ /usr/libexec/java_home -V

 

 

java JDK 기본 설정 변경하기

 

.zshrc

.zshrc파일에 아래 내용을 추가하고 터미널을 다시 실행시킨다음 java -version을 확인하면 21버전으로 변경된 것을 확인 할 수 있습니다.

$ export JAVA_HOME=$(/usr/libexec/java_home -v 21.0.2)

 

java JDK 삭제하기

경로는 본인이 설치한 위치에 따라 조금 다를 수 있습니다.

$ cd /Library/Java/JavaVirtualMachines 

$ sudo rm -rf openjdk-17.0.9

 

 

반응형

'IT' 카테고리의 다른 글

[ Mac ] 아파치 톰캣 ( Apache Tomcat ) 설치하기  (0) 2024.02.12
반응형

아파치 톰캣 ( Apache Tomcat )을 설치하는 방법입니다.

 

brew

$ brew install tomcat

 

위처럼 설치방법은 굉장히 간단합니다.

 

설치 확인

 

$ cd /opt/homebrew ~
$ cd bin

// 톰캣 실행
$ ./catalina start

// 톰캣 종료
$ ./catalina stop

 

실행후에 http://localhost:8080로 이동했을 때 아래처럼 톰캣 페이지가 보이게됩니다.

stop은 말그대로 종료입니다.

 


 

brew 설치하는 방법

 

반응형

'IT' 카테고리의 다른 글

[ Mac ] JavaJDK 설치하기  (0) 2024.02.12
반응형

React를 새로 생성했을 때 발생했던 에러입니다.

One of your dependencies, babel-preset-react-app, is importing the "@babel/plugin-proposal-private-property-in-object" package without declaring it in its dependencies. This is currently working because "@babel/plugin-proposal-private-property-in-object" is already in your node_modules folder for unrelated reasons, but it may break at any time. babel-preset-react-app is part of the create-react-app project, which is not maintianed anymore. It is thus unlikely that this bug will ever be fixed. Add "@babel/plugin-proposal-private-property-in-object" to your devDependencies to work around this error. This will make this message go away.

 

처음에는 Stack overflow에 나와있는 방법으로 해결을 했었습니다.

 

$ npm install --save-dev @babel/plugin-proposal-private-property-in-object

 

그런데 계속해서 뜨는게 거슬려서 확실하게 하고자 뭐가 문제인지 더 찾아봤습니다.

 

해결방법

여러 글들을 찾아다니다보니 npm과 create-react-app의 버전 호환 충돌일 가능성이 있다는 내용들이 있었습니다.

그래서 npm을 최신버전으로 올려주었더니 오류가 사라지는걸 확인할 수 있었습니다.

$ npm install -g npm@latest

 

 

반응형
반응형

react-native-vector-icons 사용하는 방법입니다.

Mac 기준입니다.

( icon 하나 사용하는데 하루가 걸린 기분이 듭니다... )

 

npm

$ npm install react-native-vector-icons
$ npm install -D @types/react-native-vector-icons

 

저는 typescript를 사용하고 있기 때문에 @types 부분도 추가했습니다.

 

 

iOS에서 아이콘 추가하기

 

1. 프로젝트를 열고 터미널을 열어줍니다.

$ cd ios
$ pod install

 

ios로 들어간다음 pod install을 해줍니다.

 

2. 프로젝트 폴더에서 ios -> project-name.xcworkspace를 열어줍니다. ( 대기 ! )

 

3. 다시 프로젝트 폴더에서 node_modules -> react-native-vector-icons -> Fonts 폴더를 복사합니다.

 

4. 아까 대기시켜둔 workspace에 project-name 폴더 아래에 복사한 Fonts폴더를 추가해줍니다.

추가할 때 아래 창이 뜨면 이렇게 해주면 됩니다.

 

아래처럼 추가된 모습을 볼 수 있습니다.

 

5. Fonts폴더를 추가시키고 아래를 보면 Info파일이 있습니다.

Info로 들어가서 아래처럼 내용들을 추가해줍니다.

Informations Property List
 - Fonts provided by application
   - item 0 ~ item 18 ( value에 폰트 내용을 추가합니다. )

 

Informations Property List 옆에 +를 누르고 Fonts provided by application을 추가합니다. ( 자동완성 )

Fonts provided by application 옆에 +를 누르고 item들을 추가시킨다음 Fonts내부에 ttf파일들 이름을 추가시켜줍니다.

 

추가를 하고난다음 Info를 닫고 프로젝트에서 ios -> project-name -> Info.plist파일을 열어보면 아래처럼 추가된 내용을 확인 할 수 있습니다.

 

workspace에서 project-name 폴더에 Build Phases -> Copy Bundle Resources를 확인해보면 여기도 Fonts가 추가된 것을 확인 할 수 있습니다.

 

이렇게 완료하고 ios를 재시작하면 icon이 들어오는 것을 확인 할 수 있습니다.

( 아이콘 추가방법을 가장 아래 추가해 두었습니다. )

 

 

Android에서 아이콘 추가하기

 

1. android/app/build.gradle 파일로 이동해서 아래 내용을 추가해줍니다.

apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle")

 

2. android/settings.gradle 파일로 이동해서 아래 내용을 추가해줍니다.

include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')

 

3. 다시한번 android/app/build.gradle 파일로 이동해서 아래 내용을 추가해줍니다.

dependencies {
    implementation project(':react-native-vector-icons')
    ...
}

 

dependencies 내부에 추가해줍니다.

 

4. gradlew clean를 실행합니다.

$ cd android
$ ./gradlew clean

 

pod install 한것과 같습니다.

 

이렇게 iOS, 안드로이드 모두 아이콘을 추가할 준비가 됐습니다.

 


 

아이콘 추가하기

react-native-vector-icons 바로가기

 

위 사이트에 접속하면 아래처럼 빨간배경에 제목과 아이콘 이름들을 확인 할 수 있습니다.

제목은 import할 때 사용하고 아이콘 이름은 아이콘을 추가할 때 사용하는 이름입니다.

 

 

아이콘 추가하는 방법 1

import { StyleSheet, Text, View } from "react-native";
import AntDesign from 'react-native-vector-icons/AntDesign';

const Headers = () => {
    return (
        <View>
            <Text>Header</Text>
            <AntDesign size={35} name="creditcard" color="#000000" />
        </View>
    );
}


export default Headers;

 

아이콘 추가하는 방법 2

import { StyleSheet, Text, View } from "react-native";
import Icon from 'react-native-vector-icons/FontAwesome';

const myIcon = <Icon name="rocket" size={30} color="red" />;

const Headers = () => {
    return (
        <View>
            <Text>Header</Text>
            {myIcon}
        </View>
    );
}

export default Headers;

 

 

확인해보면 아래처럼 아이콘이 추가된 것을 확인 할 수 있습니다.

 

 

 

React Native... 쉽지 않습니다..

반응형
반응형

React 프로젝트를 진행할 때 ../../../ 와 같은 상대경로가 아닌 @components/, @pages/ 와 같은 절대경로는 설정하는 방법입니다.

프로젝트를 진행할 때 간단한 페이지면 문제가 없는데, redux같은 상태관리를 사용하거나 component가 늘어나면 ../../../component/ 이런식으로 깊어질 때가 있습니다.

이럴때 절대경로를 통해서 최상위부터 시작하는 방법으로 저는 craco를 사용했습니다.

 

Craco

Craco는 Create-React-App Configuration Override의 약자입니다.

CRA를 사용할 때 webpack을 설정하려면 오버라이딩을 해야하는데 이 webpack을 직접 설정하려면 eject를 해야합니다.

하지만 그렇게하게되면 CRA의 장점들이 사라지게 됩니다. ( 트랜스파일링, 번들링 등 )

그래서 이때 Craco 라이브러리를 사용해서 eject를 하지 않고 webpack 설정을 변경해줍니다.

 

npm

$ npm install @craco/craco

 

package.json

  "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },

 

scripts부분에 react-scripts부분을 craco로 변경해주었습니다.

 

craco.config.js

const path = require('path');

module.exports = {
  webpack: {
    alias: {
      '@components': path.resolve(__dirname, 'src/components'),
      '@pages': path.resolve(__dirname, 'src/pages'),
      '@styles': path.resolve(__dirname, 'src/styles'),
      '@json': path.resolve(__dirname, 'src/json'),
      '@items': path.resolve(__dirname, 'src/items'),
    },
    "compilerOptions": {
      "baseUrl": "src"
    },
    "include": [
        "src"
    ]
  },
};

 

최상위 경로에 craco.config.js파일을 생성해서 위처럼 오버라이딩 할 설정을 해줍니다.

 

이후 저같은 경우 babel-preset-react-app, is importing the "@babel/plugin-proposal-private-property-in-object" package without declaring it in its dependencies 이런 경고창이 보였습니다.

 

저같은 경우는 stack overflow를 따라 아래처럼 해결했습니다.

stack overflow 바로가기

$ npm install --save-dev @babel/plugin-proposal-private-property-in-object

 

 

반응형

+ Recent posts