<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hiio420_official.log</title>
        <link>https://velog.io/</link>
        <description>Holla!</description>
        <lastBuildDate>Wed, 29 Oct 2025 13:50:58 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hiio420_official.log</title>
            <url>https://velog.velcdn.com/images/hiio420_official/profile/4582226b-35e3-4898-8959-9ba279efe1d7/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hiio420_official.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hiio420_official" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[# 6장｜처음부터 끝까지: LLaMA3.2 SLM 파인튜닝 → 평가 → GGUF 변환 → Ollama 실행 (풀 코드)]]></title>
            <link>https://velog.io/@hiio420_official/6%EC%9E%A5%EF%BD%9C%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%81%9D%EA%B9%8C%EC%A7%80-LLaMA3.2-SLM-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%ED%8F%89%EA%B0%80-GGUF-%EB%B3%80%ED%99%98-Ollama-%EC%8B%A4%ED%96%89-%ED%92%80-%EC%BD%94%EB%93%9C</link>
            <guid>https://velog.io/@hiio420_official/6%EC%9E%A5%EF%BD%9C%EC%B2%98%EC%9D%8C%EB%B6%80%ED%84%B0-%EB%81%9D%EA%B9%8C%EC%A7%80-LLaMA3.2-SLM-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%ED%8F%89%EA%B0%80-GGUF-%EB%B3%80%ED%99%98-Ollama-%EC%8B%A4%ED%96%89-%ED%92%80-%EC%BD%94%EB%93%9C</guid>
            <pubDate>Wed, 29 Oct 2025 13:50:58 GMT</pubDate>
            <description><![CDATA[<h1 id="6장｜처음부터-끝까지-llama32-slm-파인튜닝-→-평가-→-gguf-변환-→-ollama-실행-풀-코드">6장｜처음부터 끝까지: LLaMA3.2 SLM 파인튜닝 → 평가 → GGUF 변환 → Ollama 실행 (풀 코드)</h1>
<p>이 장은 <strong>완전 자급자족 버전</strong>입니다.
독자가 이 글만 보고도 <strong>처음부터</strong> 실행할 수 있도록, 4장(학습)과 5장(평가), 6장(배포: LoRA 병합 → GGUF 변환 → Ollama 실행)까지 한 번에 마칩니다.
모델은 <strong><code>meta-llama/Llama-3.2-1B-Instruct</code></strong> 를 사용합니다. (GGUF/Ollama 호환)</p>
<blockquote>
<p>원칙(중요): 파인튜닝에서는 <strong>토크나이저 어휘를 절대 바꾸지 않습니다.</strong>
<code>pad_token</code>은 “추가(add)”가 아니라 <strong>eos를 pad로 “지정”</strong>만 하세요. 어휘가 변하면 GGUF 변환에서 체크섬이 실패합니다.</p>
</blockquote>
<hr>
<h2 id="0-환경-준비">0) 환경 준비</h2>
<pre><code class="language-bash"># (선택) 가상환경
python -m venv venv &amp;&amp; source venv/bin/activate  # Windows: venv\Scripts\activate

# 필수 패키지
pip install -U transformers datasets peft accelerate bitsandbytes torch torchvision torchaudio</code></pre>
<blockquote>
<p>GPU가 없다면 <code>bitsandbytes</code>는 설치에 실패할 수 있어요. 상관없이 LoRA는 CPU에서도 동작하긴 하지만 느립니다. 우선 코드는 동일합니다.</p>
</blockquote>
<hr>
<h2 id="1-데이터-준비-예시-jsonl--instructionresponse-포맷">1) 데이터 준비 (예시 JSONL) — Instruction/Response 포맷</h2>
<pre><code class="language-bash">cat &gt; train.jsonl &lt;&lt; &#39;EOF&#39;
{&quot;text&quot;:&quot;### Instruction:\nExplain regression vs classification.\n### Response:\nRegression predicts continuous values; classification predicts discrete categories.&quot;}
{&quot;text&quot;:&quot;### Instruction:\nWhat is overfitting?\n### Response:\nOverfitting occurs when a model memorizes training data patterns and fails to generalize.&quot;}
{&quot;text&quot;:&quot;### Instruction:\nDefine gradient descent.\n### Response:\nAn optimization algorithm that iteratively updates parameters to minimize a loss function.&quot;}
EOF</code></pre>
<blockquote>
<p>실제로는 수천~수만 샘플을 사용하세요. 포맷만 위와 같이 맞추면 됩니다.</p>
</blockquote>
<hr>
<h2 id="part-4장-lora-파인튜닝--학습-전체-코드">PART 4장) LoRA 파인튜닝 — <strong>학습 전체 코드</strong></h2>
<pre><code class="language-python"># 4_train_lora.py
from datasets import load_dataset
from transformers import (
    AutoTokenizer, AutoModelForCausalLM,
    DataCollatorForLanguageModeling, Trainer, TrainingArguments
)
from peft import LoraConfig, get_peft_model
import torch, os, json

BASE_MODEL = &quot;meta-llama/Llama-3.2-1B-Instruct&quot;
OUT_DIR    = &quot;./lora-out&quot;

os.makedirs(OUT_DIR, exist_ok=True)

# 1) 데이터 로드
dataset = load_dataset(&quot;json&quot;, data_files=&quot;train.jsonl&quot;)

# 2) 토크나이저/모델 로드 (어휘 불변!)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=True)
if tokenizer.pad_token is None:
    # &#39;추가&#39;가 아니라 &#39;지정&#39;만! vocab 크기가 바뀌지 않음.
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(BASE_MODEL)
# 토큰 길이에 맞춰 임베딩 리사이즈 (어휘 변경이 없으므로 길이는 원본과 동일)
model.resize_token_embeddings(len(tokenizer))

# 3) LoRA 설정
lora_cfg = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=[&quot;q_proj&quot;, &quot;v_proj&quot;],  # 시작은 가볍게. 필요시 k/o_proj 추가
    lora_dropout=0.05,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;,
)
model = get_peft_model(model, lora_cfg)

# 4) 전처리 (labels = input_ids, pad=-100 마스킹)
def tok_fn(batch):
    enc = tokenizer(batch[&quot;text&quot;], truncation=True, padding=&quot;max_length&quot;, max_length=512)
    enc[&quot;labels&quot;] = [
        [t if t != tokenizer.pad_token_id else -100 for t in ids]
        for ids in enc[&quot;input_ids&quot;]
    ]
    return enc

tokenized = dataset.map(tok_fn, batched=True, remove_columns=[&quot;text&quot;])
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

# 5) 학습 인자
use_fp16 = torch.cuda.is_available()
args = TrainingArguments(
    output_dir=OUT_DIR,
    num_train_epochs=2,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    logging_steps=10,
    save_strategy=&quot;epoch&quot;,
    report_to=&quot;none&quot;,
    fp16=use_fp16,
)

# 6) 학습
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized[&quot;train&quot;],
    data_collator=collator
)
trainer.train()

# 7) 저장 (어댑터 + 토크나이저)
model.save_pretrained(f&quot;{OUT_DIR}/adapter&quot;)
tokenizer.save_pretrained(f&quot;{OUT_DIR}/tokenizer&quot;)

# 8) 메타 기록(선택: 검사용)
meta = {
    &quot;base_model&quot;: BASE_MODEL,
    &quot;tokenizer_length&quot;: len(tokenizer),
    &quot;pad_token&quot;: tokenizer.pad_token,
    &quot;pad_token_id&quot;: tokenizer.pad_token_id,
}
with open(f&quot;{OUT_DIR}/train_meta.json&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    json.dump(meta, f, ensure_ascii=False, indent=2)

print(&quot;✅ LoRA training complete. Saved to&quot;, OUT_DIR)</code></pre>
<p>실행:</p>
<pre><code class="language-bash">python 4_train_lora.py</code></pre>
<hr>
<h2 id="part-5장-성능-평가--perplexity--생성-예시-전체-코드">PART 5장) 성능 평가 — <strong>Perplexity + 생성 예시 전체 코드</strong></h2>
<pre><code class="language-python"># 5_eval_ppl_and_generate.py
from datasets import load_dataset
from transformers import (
    AutoTokenizer, AutoModelForCausalLM,
    DataCollatorForLanguageModeling, Trainer, TrainingArguments, pipeline
)
from peft import PeftModel
import torch, math, json

BASE_MODEL   = &quot;meta-llama/Llama-3.2-1B-Instruct&quot;
ADAPTER_DIR  = &quot;./lora-out/adapter&quot;
TOKENIZER_DIR= &quot;./lora-out/tokenizer&quot;

# 1) 평가 데이터 분리
raw = load_dataset(&quot;json&quot;, data_files=&quot;train.jsonl&quot;)
split = raw[&quot;train&quot;].train_test_split(test_size=0.2, seed=42)
eval_ds = split[&quot;test&quot;]

# 2) 토크나이저/모델 로드 (어휘 불변)
tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_DIR, use_fast=True)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

base = AutoModelForCausalLM.from_pretrained(BASE_MODEL)
base.resize_token_embeddings(len(tokenizer))
model = PeftModel.from_pretrained(base, ADAPTER_DIR).eval()

# 3) 전처리
def tok_eval(batch):
    enc = tokenizer(batch[&quot;text&quot;], truncation=True, padding=&quot;max_length&quot;, max_length=512)
    enc[&quot;labels&quot;] = [
        [t if t != tokenizer.pad_token_id else -100 for t in ids]
        for ids in enc[&quot;input_ids&quot;]
    ]
    return enc

eval_tok = eval_ds.map(tok_eval, batched=True, remove_columns=[&quot;text&quot;])
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

# 4) Perplexity (낮을수록 좋음)
use_fp16 = torch.cuda.is_available()
args = TrainingArguments(
    output_dir=&quot;./eval-out&quot;,
    per_device_eval_batch_size=2,
    dataloader_drop_last=False,
    report_to=&quot;none&quot;,
    fp16=use_fp16,
)

trainer = Trainer(
    model=model,
    args=args,
    eval_dataset=eval_tok,
    data_collator=collator
)

metrics = trainer.evaluate()
loss = metrics[&quot;eval_loss&quot;]
ppl  = math.exp(loss) if loss &lt; 20 else float(&quot;inf&quot;)
print(f&quot;📊 eval loss: {loss:.4f} | ppl: {ppl:.2f}&quot;)

# 5) 생성 예시 (정성 평가)
prompt = &quot;&quot;&quot;### Instruction:
Explain the difference between regression and classification.
### Response:
&quot;&quot;&quot;

gen = pipeline(
    &quot;text-generation&quot;,
    model=model,
    tokenizer=tokenizer,
    device_map=&quot;auto&quot; if torch.cuda.is_available() else None
)
out = gen(prompt, max_new_tokens=120, do_sample=False)[0][&quot;generated_text&quot;]
print(&quot;=== Sample Output ===\n&quot;, out)</code></pre>
<p>실행:</p>
<pre><code class="language-bash">python 5_eval_ppl_and_generate.py</code></pre>
<hr>
<h2 id="part-6장-배포--lora-병합-→-gguf-변환-→-ollama-실행-풀-코드명령">PART 6장) 배포 — <strong>LoRA 병합 → GGUF 변환 → Ollama 실행 (풀 코드/명령)</strong></h2>
<blockquote>
<p><strong>중요:</strong> 여기서부터는 <strong>토크나이저가 섞이지 않도록</strong> “베이스 기준”으로 <strong>다시</strong> 로드 후 병합합니다.
(간혹 <code>./lora-out/tokenizer</code> 안에 다른 계열 토크나이저가 섞이면 어휘 크기(128000 ↔ 50265) 불일치가 납니다)</p>
</blockquote>
<h3 id="6-1-lora-병합-완전-가중치">6-1) LoRA 병합 (완전 가중치)</h3>
<pre><code class="language-python"># 6_merge_lora_clean.py
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import os

BASE_MODEL  = &quot;meta-llama/Llama-3.2-1B-Instruct&quot;
ADAPTER_DIR = &quot;./lora-out/adapter&quot;
MERGED_DIR  = &quot;./merged-llama&quot;

# 1) 베이스 기준 토크나이저/모델 로드
tok = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=True)
if tok.pad_token is None:
    tok.pad_token = tok.eos_token  # &#39;추가&#39; 아님. 어휘 불변

print(&quot;Base tokenizer vocab_size:&quot;, tok.vocab_size)  # LLaMA3.2는 128000 여야 정상

base = AutoModelForCausalLM.from_pretrained(BASE_MODEL)
base.resize_token_embeddings(len(tok))

# 2) LoRA 어댑터 병합
model = PeftModel.from_pretrained(base, ADAPTER_DIR)
model = model.merge_and_unload()

# 3) 저장(베이스 기준으로 깨끗하게)
os.makedirs(MERGED_DIR, exist_ok=True)
model.save_pretrained(MERGED_DIR)
tok.save_pretrained(MERGED_DIR)

print(&quot;✅ merged model saved to:&quot;, MERGED_DIR)</code></pre>
<p>실행:</p>
<pre><code class="language-bash">python 6_merge_lora_clean.py</code></pre>
<h3 id="6-2-gguf-변환-llamacpp-최신본">6-2) GGUF 변환 (llama.cpp 최신본)</h3>
<pre><code class="language-bash"># llama.cpp 최신 리포 준비
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
python -m pip install -U pip wheel setuptools
python -m pip install -U -r requirements.txt
python -m pip install -U tiktoken tokenizers transformers sentencepiece protobuf

# GGUF로 변환 (상위 폴더에 결과 생성)
python ./convert-hf-to-gguf.py ../merged-llama --outfile ../merged-llama.gguf --verbose</code></pre>
<blockquote>
<p>실패 시 대부분 <strong>토크나이저가 섞였거나</strong> <code>llama.cpp</code>가 구버전입니다.
위 6-1 과정을 다시 정확히 실행하고, 최신본으로 재시도하세요.</p>
</blockquote>
<h3 id="6-3-modelfile-작성--ollama-등록">6-3) Modelfile 작성 &amp; Ollama 등록</h3>
<p><code>Modelfile</code> (프로젝트 루트에 저장)</p>
<pre><code class="language-Dockerfile">FROM ./merged-llama.gguf

PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 4096

TEMPLATE &quot;&quot;&quot;### Instruction:
{{prompt}}

### Response:&quot;&quot;&quot;</code></pre>
<p>등록 &amp; 실행:</p>
<pre><code class="language-bash">ollama create my-llama -f Modelfile
ollama run my-llama &quot;Explain gradient descent simply.&quot;</code></pre>
<hr>
<h2 id="부록-토크나이저-섞임-방지-가드-스니펫">(부록) 토크나이저 섞임 방지 가드 스니펫</h2>
<blockquote>
<p><strong>언제든 실행해서</strong> 베이스/병합 토크나이저의 <strong>vocab 크기(128000)</strong> 를 확인하세요.</p>
</blockquote>
<pre><code class="language-python"># tokenizer_guard.py
from transformers import AutoTokenizer
BASE_MODEL = &quot;meta-llama/Llama-3.2-1B-Instruct&quot;
MERGED_DIR = &quot;./merged-llama&quot;

tb = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=True)
if tb.pad_token is None: tb.pad_token = tb.eos_token
tm = AutoTokenizer.from_pretrained(MERGED_DIR, use_fast=True)
if tm.pad_token is None: tm.pad_token = tm.eos_token

print(&quot;base vocab:&quot;, tb.vocab_size, &quot;| merged vocab:&quot;, tm.vocab_size)
assert tb.vocab_size == tm.vocab_size == 128000, &quot;Tokenizer vocab mismatch. Clean and redo merge.&quot;
print(&quot;✅ tokenizer sizes aligned (128000).&quot;)</code></pre>
<hr>
<h2 id="마무리">마무리</h2>
<p>이 글 하나로 다음을 끝냈습니다.</p>
<ol>
<li><strong>LoRA 파인튜닝</strong>(4장 풀 코드)</li>
<li><strong>Perplexity/생성 평가</strong>(5장 풀 코드)</li>
<li><strong>LoRA 병합 → GGUF 변환 → Ollama 실행</strong>(6장 풀 코드 + 명령)</li>
</ol>
<p>핵심은 <strong>토크나이저 불변성</strong>입니다.
<code>pad_token</code>은 <strong>추가가 아니라 지정</strong>, <code>add_tokens()/train_new_from_iterator()</code> 는 <strong>파인튜닝에선 금지</strong> — 이 원칙만 지키면 GGUF 변환과 Ollama 실행은 매끄럽게 이어집니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[4–5장｜LoRA 파인튜닝과 모델 성능 평가 — SLM + Hugging Face + Ollama 실습 완결판]]></title>
            <link>https://velog.io/@hiio420_official/45%EC%9E%A5%EF%BD%9CLoRA-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D%EA%B3%BC-%EB%AA%A8%EB%8D%B8-%EC%84%B1%EB%8A%A5-%ED%8F%89%EA%B0%80-SLM-Hugging-Face-Ollama-%EC%8B%A4%EC%8A%B5-%EC%99%84%EA%B2%B0%ED%8C%90</link>
            <guid>https://velog.io/@hiio420_official/45%EC%9E%A5%EF%BD%9CLoRA-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D%EA%B3%BC-%EB%AA%A8%EB%8D%B8-%EC%84%B1%EB%8A%A5-%ED%8F%89%EA%B0%80-SLM-Hugging-Face-Ollama-%EC%8B%A4%EC%8A%B5-%EC%99%84%EA%B2%B0%ED%8C%90</guid>
            <pubDate>Tue, 28 Oct 2025 06:24:20 GMT</pubDate>
            <description><![CDATA[<h1 id="45장｜lora-파인튜닝과-모델-성능-평가--slm--hugging-face--ollama-실습-완결판">4–5장｜LoRA 파인튜닝과 모델 성능 평가 — SLM + Hugging Face + Ollama 실습 완결판</h1>
<p>앞선 장에서 우리는 SLM(Small Language Model)과 Ollama 환경을 구축하고,
Hugging Face 모델을 불러오는 과정을 모두 끝냈다.
이제는 그 모델을 <strong>내 데이터로 파인튜닝(fine-tuning)</strong> 하고,
그 결과를 <strong>Perplexity 및 생성 예시로 평가</strong>한다.</p>
<p>이번 장은 두 파트로 구성된다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td><strong>4장</strong></td>
<td>LoRA/QLoRA 기반 파인튜닝 수행</td>
</tr>
<tr>
<td><strong>5장</strong></td>
<td>학습 결과 모델의 정량·정성 평가 및 로그 관리</td>
</tr>
</tbody></table>
<hr>
<h2 id="part-1--4장--lora-파인튜닝-수행">PART 1 — 4장 : LoRA 파인튜닝 수행</h2>
<hr>
<h3 id="1-환경-설정">1. 환경 설정</h3>
<pre><code class="language-bash">pip install -U transformers datasets peft accelerate bitsandbytes</code></pre>
<ul>
<li><strong>transformers</strong> — Hugging Face 모델 관리</li>
<li><strong>datasets</strong> — 데이터셋 로딩 및 전처리</li>
<li><strong>peft</strong> — LoRA/QLoRA 어댑터 관리</li>
<li><strong>bitsandbytes</strong> — 4bit/8bit 양자화 지원 (GPU에서만 사용)</li>
</ul>
<hr>
<h3 id="2-데이터-로드-및-전처리">2. 데이터 로드 및 전처리</h3>
<p>이전 장에서 만든 <code>train.jsonl</code> 파일에는 instruction–response 쌍이 포함되어 있다.</p>
<pre><code class="language-python">from datasets import load_dataset

dataset = load_dataset(&quot;json&quot;, data_files=&quot;train.jsonl&quot;)

print(dataset)
print(dataset[&quot;train&quot;][0])</code></pre>
<p>데이터는 다음과 같이 구성되어 있어야 한다.</p>
<pre><code class="language-json">{&quot;text&quot;: &quot;### Instruction:\nExplain regression vs classification.\n### Response:\nRegression predicts continuous values...&quot;}</code></pre>
<hr>
<h3 id="3-토크나이저-및-모델-준비">3. 토크나이저 및 모델 준비</h3>
<pre><code class="language-python">from transformers import AutoTokenizer, AutoModelForCausalLM

BASE_MODEL = &quot;facebook/opt-1.3b&quot;
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=True)

# pad_token 지정
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

model = AutoModelForCausalLM.from_pretrained(BASE_MODEL)
model.resize_token_embeddings(len(tokenizer))</code></pre>
<hr>
<h3 id="4-lora-설정-및-적용">4. LoRA 설정 및 적용</h3>
<pre><code class="language-python">from peft import LoraConfig, get_peft_model

lora_cfg = LoraConfig(
    r=8,                        # 랭크 (조정 강도)
    lora_alpha=16,              # LoRA 스케일링
    target_modules=[&quot;q_proj&quot;, &quot;v_proj&quot;],
    lora_dropout=0.05,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;,
)

model = get_peft_model(model, lora_cfg)
print(&quot;LoRA 설정 완료&quot;)</code></pre>
<hr>
<h3 id="5-데이터-토크나이징-및-라벨-생성">5. 데이터 토크나이징 및 라벨 생성</h3>
<p>언어모델은 <code>labels</code>가 필요하다.
<code>labels = input_ids</code>로 설정하되, padding 부분은 <code>-100</code>으로 마스킹한다.</p>
<pre><code class="language-python">def tokenize_function(batch):
    enc = tokenizer(batch[&quot;text&quot;], truncation=True, padding=&quot;max_length&quot;, max_length=512)
    labels = [
        [tok if tok != tokenizer.pad_token_id else -100 for tok in ids]
        for ids in enc[&quot;input_ids&quot;]
    ]
    enc[&quot;labels&quot;] = labels
    return enc

tokenized = dataset.map(tokenize_function, batched=True, remove_columns=[&quot;text&quot;])</code></pre>
<hr>
<h3 id="6-학습-실행">6. 학습 실행</h3>
<pre><code class="language-python">from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
import torch

args = TrainingArguments(
    output_dir=&quot;./lora-out&quot;,
    num_train_epochs=2,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    logging_steps=20,
    save_strategy=&quot;epoch&quot;,
    report_to=&quot;none&quot;,
    fp16=torch.cuda.is_available(),
)

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized[&quot;train&quot;],
    data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False),
)

trainer.train()</code></pre>
<blockquote>
<p>첫 학습에서는 작은 데이터(100개 이하)로 테스트한 뒤 전체 데이터로 확장하는 것이 좋다.</p>
</blockquote>
<hr>
<h3 id="7-모델-저장">7. 모델 저장</h3>
<pre><code class="language-python">model.save_pretrained(&quot;./lora-out/adapter&quot;)
tokenizer.save_pretrained(&quot;./lora-out/tokenizer&quot;)
print(&quot;파인튜닝 모델 저장 완료&quot;)</code></pre>
<p>이제 학습이 끝났으니, 다음 단계로 넘어가자.
모델이 얼마나 좋아졌는지를 평가해보는 것이다.</p>
<hr>
<h2 id="part-2--5장--파인튜닝-모델-평가와-결과-분석">PART 2 — 5장 : 파인튜닝 모델 평가와 결과 분석</h2>
<hr>
<h3 id="1-평가용-데이터셋-구성">1. 평가용 데이터셋 구성</h3>
<p>훈련용 데이터에서 5%를 분리하여 평가용으로 사용한다.</p>
<pre><code class="language-python">from datasets import load_dataset

dataset = load_dataset(&quot;json&quot;, data_files=&quot;train.jsonl&quot;)
split = dataset[&quot;train&quot;].train_test_split(test_size=0.05, seed=42)
eval_data = split[&quot;test&quot;]

print(f&quot;평가 데이터 수: {len(eval_data)}&quot;)</code></pre>
<hr>
<h3 id="2-모델과-토크나이저-불러오기">2. 모델과 토크나이저 불러오기</h3>
<pre><code class="language-python">from transformers import AutoModelForCausalLM, AutoTokenizer, DataCollatorForLanguageModeling
from peft import PeftModel
import torch

BASE_MODEL = &quot;facebook/opt-1.3b&quot;
ADAPTER_DIR = &quot;./lora-out/adapter&quot;
TOKENIZER_DIR = &quot;./lora-out/tokenizer&quot;

# 토크나이저 로드
tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_DIR, use_fast=True)
tokenizer.pad_token = tokenizer.eos_token

# 기본 모델 로드
base_model = AutoModelForCausalLM.from_pretrained(BASE_MODEL)

# 토크나이저의 어휘 크기에 맞춰 모델의 임베딩 레이어 크기 조정
base_model.resize_token_embeddings(len(tokenizer))

# LoRA 어댑터 로드
model = PeftModel.from_pretrained(base_model, ADAPTER_DIR)
model.eval()
</code></pre>
<hr>
<h3 id="3-perplexity-계산">3. Perplexity 계산</h3>
<pre><code class="language-python">from transformers import Trainer, TrainingArguments
import math

def tokenize_eval(batch):
    enc = tokenizer(batch[&quot;text&quot;], truncation=True, padding=&quot;max_length&quot;, max_length=512)
    enc[&quot;labels&quot;] = [
        [tok if tok != tokenizer.pad_token_id else -100 for tok in ids]
        for ids in enc[&quot;input_ids&quot;]
    ]
    return enc

eval_tokenized = eval_data.map(tokenize_eval, batched=True, remove_columns=[&quot;text&quot;])
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

args = TrainingArguments(
    output_dir=&quot;./eval-out&quot;,
    per_device_eval_batch_size=2,
    dataloader_drop_last=False,
    report_to=&quot;none&quot;,
    fp16=torch.cuda.is_available(),
)

trainer = Trainer(
    model=model,
    args=args,
    eval_dataset=eval_tokenized,
    data_collator=collator,
)

result = trainer.evaluate()
loss = result[&quot;eval_loss&quot;]
ppl = math.exp(loss)

print(f&quot;평가 손실(loss): {loss:.4f}&quot;)
print(f&quot;Perplexity(PPL): {ppl:.2f}&quot;)</code></pre>
<hr>
<h3 id="4-샘플-생성-비교">4. 샘플 생성 비교</h3>
<pre><code class="language-python">from transformers import pipeline

prompt = &quot;&quot;&quot;### Instruction:
Explain the difference between regression and classification.
### Response:
&quot;&quot;&quot;

gen = pipeline(
    &quot;text-generation&quot;,
    model=model,
    tokenizer=tokenizer,
    device_map=&quot;auto&quot; if torch.cuda.is_available() else None
)

output = gen(prompt, max_new_tokens=120, do_sample=False)[0][&quot;generated_text&quot;]
print(&quot;=== 모델 생성 결과 ===\n&quot;)
print(output)</code></pre>
<p>이제 파인튜닝된 모델이 instruction-response 구조를 명확히 따르고,
훈련 데이터의 문체를 반영하는지 직접 확인할 수 있다.</p>
<hr>
<h3 id="5-실험-로그-저장">5. 실험 로그 저장</h3>
<pre><code class="language-python">import csv, os, time
from pathlib import Path

Path(&quot;experiments&quot;).mkdir(exist_ok=True)
log_file = &quot;experiments/results.csv&quot;
new_file = not os.path.exists(log_file)

row = [int(time.time()), BASE_MODEL, ADAPTER_DIR, f&quot;{loss:.4f}&quot;, f&quot;{ppl:.2f}&quot;]
fields = [&quot;timestamp&quot;, &quot;model&quot;, &quot;adapter&quot;, &quot;eval_loss&quot;, &quot;ppl&quot;]

with open(log_file, &quot;a&quot;, newline=&quot;&quot;, encoding=&quot;utf-8&quot;) as f:
    w = csv.writer(f)
    if new_file:
        w.writerow(fields)
    w.writerow(row)

print(f&quot;평가 결과가 &#39;{log_file}&#39;에 저장되었습니다.&quot;)</code></pre>
<hr>
<h3 id="6-결론-및-다음-단계">6. 결론 및 다음 단계</h3>
<p>지금까지 우리는</p>
<ul>
<li><strong>Hugging Face 모델</strong>을 이용해 LoRA 방식으로 SLM을 파인튜닝하고,</li>
<li><strong>Perplexity로 정량 평가</strong>,</li>
<li><strong>샘플 생성으로 정성 평가</strong>를 수행했으며,</li>
<li>결과를 CSV로 자동 기록했다.</li>
</ul>
<p>다음 6장에서는 이 모델을 <strong>Ollama에서 직접 실행 가능한 형태(GGUF 변환 + Modelfile 생성)</strong> 로 내보내는 배포 단계를 다룬다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[# 4장｜SLM 파인튜닝 실습]]></title>
            <link>https://velog.io/@hiio420_official/4%EC%9E%A5%EF%BD%9CSLM-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@hiio420_official/4%EC%9E%A5%EF%BD%9CSLM-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Mon, 27 Oct 2025 05:07:56 GMT</pubDate>
            <description><![CDATA[<h1 id="4장｜slm-파인튜닝-실습">4장｜SLM 파인튜닝 실습</h1>
<h2 id="1-환경-준비필수-패키지">1. 환경 준비(필수 패키지)</h2>
<pre><code class="language-bash">pip install -U transformers datasets peft accelerate bitsandbytes
# GPU가 없다면 bitsandbytes는 생략 가능(QLoRA 미사용)</code></pre>
<blockquote>
<p>Python 3.10+ 권장. GPU가 없으면 <code>QLoRA</code> 대신 <code>LoRA</code>만 시도하세요(속도 이슈 고려).</p>
</blockquote>
<hr>
<h2 id="2-데이터-불러오기3장의-trainjsonl-사용">2. 데이터 불러오기(3장의 <code>train.jsonl</code> 사용)</h2>
<ul>
<li>3장에서 만든 <code>train.jsonl</code>(한 줄에 <code>{&quot;text&quot;: &quot;...&quot;}</code>형태)을 그대로 사용한다.</li>
</ul>
<pre><code class="language-python">from datasets import load_dataset

dataset = load_dataset(&quot;json&quot;, data_files=&quot;train.jsonl&quot;)
# 작은 샘플로 먼저 확인하고 싶다면:
# dataset[&quot;train&quot;] = dataset[&quot;train&quot;].select(range(200))</code></pre>
<hr>
<h2 id="3-토크나이저모델-준비--labels-생성">3. 토크나이저/모델 준비 + “labels” 생성</h2>
<ul>
<li><strong>핵심</strong>: <code>labels = input_ids</code> + 패딩 토큰 위치는 <code>-100</code>으로 마스킹</li>
</ul>
<pre><code class="language-python">from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForLanguageModeling

# SLM 계열: 크기가 너무 큰 모델은 내 머신에서 과부하가 날 수 있습니다.
# 예시 1) OPT 1.3B
model_name = &quot;facebook/opt-1.3b&quot;

tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)
# pad 토큰 지정(없으면 eos를 pad로 사용)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

def tokenize_and_label(batch):
    enc = tokenizer(
        batch[&quot;text&quot;],
        truncation=True,
        padding=&quot;max_length&quot;,
        max_length=512,
    )
    # labels = input_ids (언어모델용 next-token 예측)
    labels = []
    for ids in enc[&quot;input_ids&quot;]:
        # pad 토큰 위치는 -100으로 마스킹 → loss에서 제외
        labels.append([tok if tok != tokenizer.pad_token_id else -100 for tok in ids])
    enc[&quot;labels&quot;] = labels
    return enc

tokenized = dataset.map(tokenize_and_label, batched=True, remove_columns=[&quot;text&quot;])

# causal LM용 collator (MLM=False 중요)
collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

# 모델 로드(LoRA 적용 전 base 모델)
base_model = AutoModelForCausalLM.from_pretrained(model_name)
# 토큰 개수 변경 시(위에서 pad_token 추가) 안전하게 resize
base_model.resize_token_embeddings(len(tokenizer))</code></pre>
<hr>
<h2 id="4-lora-적용메모리-절약형-미세조정">4. LoRA 적용(메모리 절약형 미세조정)</h2>
<pre><code class="language-python">from peft import LoraConfig, get_peft_model

lora_cfg = LoraConfig(
    r=8,
    lora_alpha=16,
    target_modules=[&quot;q_proj&quot;, &quot;v_proj&quot;],  # OPT/LLaMA 계열에서 널리 쓰는 패턴
    lora_dropout=0.05,
    bias=&quot;none&quot;,
    task_type=&quot;CAUSAL_LM&quot;,
)

lora_model = get_peft_model(base_model, lora_cfg)</code></pre>
<blockquote>
<p>모델 유형에 따라 <code>target_modules</code>가 다를 수 있다. 만약 architecture가 달라 에러가 나면
<code>[&#39;q_proj&#39;,&#39;k_proj&#39;,&#39;v_proj&#39;,&#39;o_proj&#39;]</code> 중 존재하는 레이어로 조정하라.</p>
</blockquote>
<hr>
<h2 id="5-학습-루프trainer">5. 학습 루프(Trainer)</h2>
<pre><code class="language-python">from transformers import Trainer, TrainingArguments
import torch

# bfloat16/float16 자동 선택
use_bf16 = torch.cuda.is_available() and torch.cuda.get_device_capability(0)[0] &gt;= 8
use_fp16 = torch.cuda.is_available() and not use_bf16

args = TrainingArguments(
    output_dir=&quot;./lora-out&quot;,
    num_train_epochs=2,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    logging_steps=20,
    save_strategy=&quot;epoch&quot;,
    save_total_limit=2,
    report_to=&quot;none&quot;,
    bf16=use_bf16,
    fp16=use_fp16,
    optim=&quot;paged_adamw_8bit&quot; if torch.cuda.is_available() else &quot;adamw_torch&quot;,
)

trainer = Trainer(
    model=lora_model,
    args=args,
    train_dataset=tokenized[&quot;train&quot;],
    data_collator=collator,
)
trainer.train()</code></pre>
<ul>
<li>이 시점에서 <strong>loss가 정상적으로 출력</strong>되어야 한다.</li>
<li>CPU만 있는 환경에서는 시간이 오래 걸린다. 처음엔 <code>select(range(200))</code>로 200개 정도만 학습해 <strong>파이프라인 정상 작동</strong> 먼저 확인하는 것을 권장.</li>
</ul>
<hr>
<h2 id="6-qlora선택--gpu-메모리가-아주-적을-때">6. QLoRA(선택) — GPU 메모리가 아주 적을 때</h2>
<pre><code class="language-python">import torch
from peft import prepare_model_for_kbit_training
from transformers import BitsAndBytesConfig

bnb_cfg = BitsAndBytesConfig(
    load_in_4bit=True,                      # 4비트 양자화
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type=&quot;nf4&quot;,
    bnb_4bit_compute_dtype=torch.float16,
)

qlora_base = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_cfg,
    device_map=&quot;auto&quot;,
)
qlora_base.resize_token_embeddings(len(tokenizer))
qlora_base = prepare_model_for_kbit_training(qlora_base)
qlora_model = get_peft_model(qlora_base, lora_cfg)

trainer = Trainer(
    model=qlora_model,
    args=args,
    train_dataset=tokenized[&quot;train&quot;],
    data_collator=collator,
)
trainer.train()</code></pre>
<blockquote>
<p>GPU가 없다면 QLoRA는 불가(또는 무의미). 이 경우 LoRA만 사용.</p>
</blockquote>
<hr>
<h2 id="7-저장어댑터토크나이저">7. 저장(어댑터/토크나이저)</h2>
<pre><code class="language-python">lora_model.save_pretrained(&quot;./lora-out/adapter&quot;)  # 또는 qlora_model
tokenizer.save_pretrained(&quot;./lora-out/tokenizer&quot;)</code></pre>
<ul>
<li>후속 장에서 이 LoRA 어댑터를 <strong>base 모델에 merge → GGUF 변환 → Ollama용 Modelfile 생성</strong>까지 진행한다.</li>
<li><strong>주의(중요)</strong>: Ollama는 <strong>Hugging Face LoRA 어댑터를 곧바로 읽지 않는다.</strong> 다음 장에서 <strong>LoRA merge → GGUF 변환</strong> 절차를 반드시 수행한다.</li>
</ul>
<hr>
<h2 id="8-간단-검증학습-전후-비교">8. 간단 검증(학습 전후 비교)</h2>
<p>학습 전/후 비교는 간단한 프롬프트로도 감이 온다. (정교 평가는 다음 장)</p>
<pre><code class="language-python">from transformers import pipeline

gen = pipeline(&quot;text-generation&quot;, model=lora_model, tokenizer=tokenizer, device_map=&quot;auto&quot;)
print(gen(&quot;### Instruction:\nExplain the difference between regression and classification.\n### Response:\n&quot;, 
          max_new_tokens=80)[0][&quot;generated_text&quot;])</code></pre>
<hr>
<h2 id="9-정리--이번-수정에서-달라진-점">9. 정리 — 이번 수정에서 달라진 점</h2>
<ul>
<li><strong>오류 해결</strong>: <code>labels</code>가 없어서 loss가 없던 문제를 해결 → 학습(loss) 정상 출력</li>
<li><strong>안전 장치</strong>: pad_token 지정 및 <code>-100</code> 마스킹, collator 설정(mlm=False)</li>
<li><strong>실행 신뢰성</strong>: GPU/CPU 환경 모두 고려한 설정(bf16/fp16 자동)</li>
</ul>
<p>다음 5장에서는 <strong>성능 평가와 실험 추적(Perplexity/ROUGE 등)</strong> 을 다루고,
6장에서는 <strong>LoRA 병합 → GGUF 변환 → Ollama용 모형 생성(Modelfile) → 로컬 실행</strong>을 마무리한다.</p>
<hr>
<h3 id="참고문서가이드">참고(문서/가이드)</h3>
<ul>
<li>Hugging Face Transformers — Causal LM 학습에서 <code>labels</code> 필요성, <code>DataCollatorForLanguageModeling(mlm=False)</code> 사용</li>
<li>PEFT(LoRA/QLoRA) 기본 개념 및 사용법</li>
<li>bitsandbytes — 4bit 양자화 설정(BitsAndBytesConfig)</li>
</ul>
<blockquote>
<p>위 참고 문서는 변동될 수 있으니, 최신 버전 기준의 <strong>Transformers / PEFT 공식 문서</strong>를 확인해 주세요.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[
3장｜데이터 준비와 전처리 – Hugging Face + Ollama 파인튜닝 실습]]></title>
            <link>https://velog.io/@hiio420_official/3%EC%9E%A5%EF%BD%9C%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A4%80%EB%B9%84%EC%99%80-%EC%A0%84%EC%B2%98%EB%A6%AC-Hugging-Face-Ollama-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@hiio420_official/3%EC%9E%A5%EF%BD%9C%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A4%80%EB%B9%84%EC%99%80-%EC%A0%84%EC%B2%98%EB%A6%AC-Hugging-Face-Ollama-%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Thu, 23 Oct 2025 05:55:51 GMT</pubDate>
            <description><![CDATA[<h1 id="3장｜데이터-준비와-전처리--hugging-face--ollama-파인튜닝-실습">3장｜데이터 준비와 전처리 – Hugging Face + Ollama 파인튜닝 실습</h1>
<p>언어모델을 잘 학습시키는 데에는 “좋은 데이터”가 필수다.
데이터는 모델의 지식, 스타일, 응답 품질을 결정짓는다.
이 장에서는 <strong>Hugging Face datasets를 이용한 데이터 준비</strong>와,
<strong>Ollama로 학습 가능한 포맷(GGUF · GGML 변환 전 단계)</strong> 을 만드는 과정을 다룬다.</p>
<hr>
<h2 id="1-데이터-구조-설계">1. 데이터 구조 설계</h2>
<p>파인튜닝을 위한 데이터는 보통 아래 세 가지 형태 중 하나로 구성된다.</p>
<table>
<thead>
<tr>
<th>목적</th>
<th>입력 구조</th>
<th>출력 구조</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>대화형(Chat)</strong></td>
<td>instruction / input</td>
<td>response</td>
<td>“질문-답변”, “프롬프트-출력”</td>
</tr>
<tr>
<td><strong>분류(Classification)</strong></td>
<td>text</td>
<td>label</td>
<td>감정, 주제, 긍정/부정</td>
</tr>
<tr>
<td><strong>요약(Summarization)</strong></td>
<td>document</td>
<td>summary</td>
<td>문서 → 요약문</td>
</tr>
</tbody></table>
<p>이번 장에서는 <strong>Instruction – Response 구조</strong>를 기준으로 실습한다.
이 형태는 Ollama의 대화형 모델에도 그대로 적용할 수 있다.</p>
<hr>
<h2 id="2-hugging-face-datasets로-데이터-불러오기">2. Hugging Face datasets로 데이터 불러오기</h2>
<p>Hugging Face datasets 라이브러리를 사용하면 이미 공개된 데이터셋을 바로 로드할 수 있다.
예를 들어 <code>databricks/databricks-dolly-15k</code>는 대표적인 instruction – response 구조 데이터다.</p>
<pre><code class="language-python">from datasets import load_dataset

dataset = load_dataset(&quot;databricks/databricks-dolly-15k&quot;)
print(dataset)
print(dataset[&#39;train&#39;][0])</code></pre>
<p>출력 예시:</p>
<pre><code class="language-python">{
 &#39;instruction&#39;: &#39;Explain the difference between regression and classification.&#39;,
 &#39;context&#39;: &#39;&#39;,
 &#39;response&#39;: &#39;Regression predicts continuous values, classification predicts discrete labels.&#39;
}</code></pre>
<blockquote>
<p>📘 참고: <a href="https://huggingface.co/datasets/databricks/databricks-dolly-15k">https://huggingface.co/datasets/databricks/databricks-dolly-15k</a></p>
</blockquote>
<hr>
<h2 id="3-데이터-필터링-및-정제">3. 데이터 필터링 및 정제</h2>
<p>데이터셋을 그대로 사용하기보다 다음 과정을 수행하는 것이 좋다.</p>
<pre><code class="language-python">def clean_text(example):
    example[&quot;instruction&quot;] = example[&quot;instruction&quot;].strip()
    example[&quot;response&quot;] = example[&quot;response&quot;].strip()
    return example

dataset = dataset.map(clean_text)
dataset = dataset.filter(lambda x: len(x[&quot;instruction&quot;]) &gt; 5 and len(x[&quot;response&quot;]) &gt; 5)
print(f&quot;After cleaning: {len(dataset[&#39;train&#39;])} samples&quot;)</code></pre>
<blockquote>
<ul>
<li>공백 · 특수문자 제거</li>
<li>너무 짧은 샘플 제거</li>
<li>불필요한 HTML tag / 이모지 제거</li>
</ul>
</blockquote>
<p>이 과정을 통해 학습 안정성이 높아진다.</p>
<hr>
<h2 id="4-데이터셋-포맷-맞추기-instruction--response-→-text">4. 데이터셋 포맷 맞추기 (Instruction – Response → Text)</h2>
<p>Ollama의 파인튜닝 입력 포맷은 단일 텍스트로 구성된다.
따라서 아래처럼 instruction과 response를 하나의 문장으로 병합한다.</p>
<pre><code class="language-python">def format_chat(example):
    instruction = f&quot;### Instruction:\n{example[&#39;instruction&#39;]}\n&quot;
    response = f&quot;### Response:\n{example[&#39;response&#39;]}\n&quot;
    example[&quot;text&quot;] = instruction + response
    return example

dataset = dataset.map(format_chat)
dataset = dataset.remove_columns([&#39;instruction&#39;, &#39;context&#39;, &#39;response&#39;])</code></pre>
<p>이제 모델 입력으로 사용할 컬럼은 <code>text</code> 하나뿐이다.</p>
<hr>
<h2 id="5-데이터셋-저장">5. 데이터셋 저장</h2>
<p>파인튜닝 단계에서 Ollama 또는 Transformers Trainer가 접근할 수 있도록 JSONL 형태로 저장한다.</p>
<pre><code class="language-python">import json

with open(&quot;train.jsonl&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
    for item in dataset[&quot;train&quot;]:
        f.write(json.dumps({&quot;text&quot;: item[&quot;text&quot;]}, ensure_ascii=False) + &quot;\n&quot;)</code></pre>
<p>출력 예시:</p>
<pre><code class="language-json">{&quot;text&quot;: &quot;### Instruction:\nExplain the difference...\n### Response:\nRegression predicts...&quot;}</code></pre>
<blockquote>
<p>💡 Ollama는 이후 <code>Modelfile</code> 내에서 이 JSONL을 <code>FROM</code> 또는 <code>SYSTEM</code> 명령으로 불러올 수 있다.</p>
</blockquote>
<hr>
<h2 id="6-파인튜닝-전-검증용-샘플-출력">6. 파인튜닝 전 검증용 샘플 출력</h2>
<p>데이터가 잘 포맷팅되었는지 샘플을 직접 확인하자.</p>
<pre><code class="language-python">with open(&quot;train.jsonl&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
    for _ in range(3):
        print(f.readline())</code></pre>
<p>출력 결과가 “Instruction → Response” 순서로 표시되면 정상이다.</p>
<hr>
<h2 id="7-데이터-품질-체크포인트">7. 데이터 품질 체크포인트</h2>
<p>좋은 파인튜닝 데이터의 핵심 기준은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>일관성(Consistency)</strong></td>
<td>Instruction과 Response가 논리적으로 이어지는가</td>
</tr>
<tr>
<td><strong>균형(Balance)</strong></td>
<td>특정 주제나 문체에 치우치지 않는가</td>
</tr>
<tr>
<td><strong>길이(Length)</strong></td>
<td>너무 짧거나 너무 긴 문장이 과도하지 않은가</td>
</tr>
<tr>
<td><strong>정확성(Accuracy)</strong></td>
<td>오탈자, 잘못된 사실이 포함되지 않았는가</td>
</tr>
</tbody></table>
<p>데이터의 품질이 학습 효과의 70%를 결정한다는 점을 명심해야 한다.</p>
<hr>
<h2 id="8-ollama용-데이터-경로-설정">8. Ollama용 데이터 경로 설정</h2>
<p>학습 준비가 끝났다면, <code>Modelfile</code>에서 학습 데이터 경로를 지정한다.</p>
<pre><code class="language-Dockerfile">FROM llama3.2
SYSTEM &quot;Fine-tuning with custom dataset&quot;
DATASET &quot;./train.jsonl&quot;
TEMPLATE &quot;### Instruction:\n{{prompt}}\n### Response:\n&quot;</code></pre>
<blockquote>
<p>다음 장에서 이 <code>Modelfile</code> 을 이용해 <strong>실제 파인튜닝 명령(<code>ollama create</code>)을 실행</strong>하게 된다.</p>
</blockquote>
<hr>
<h2 id="9-마무리">9. 마무리</h2>
<p>이 장에서는 Hugging Face 데이터셋을 가져와,
Instruction – Response 형태로 정리하고,
Ollama 파인튜닝이 바로 가능한 JSONL 포맷으로 변환하는 과정을 마쳤다.</p>
<p>이제 다음 4장에서 본격적으로 <strong>모델 파인튜닝(Fine-Tuning)</strong> 을 진행한다.
LoRA 방식, QLoRA 경량화, 학습률 / 에폭 조정 / 체크포인트 저장 등을 단계별로 다룰 것이다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[2장｜학습을 위한 환경 준비와 Hugging Face 모델 불러오기
]]></title>
            <link>https://velog.io/@hiio420_official/2%EC%9E%A5%EF%BD%9C%ED%95%99%EC%8A%B5%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%99%98%EA%B2%BD-%EC%A4%80%EB%B9%84%EC%99%80-Hugging-Face-%EB%AA%A8%EB%8D%B8-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hiio420_official/2%EC%9E%A5%EF%BD%9C%ED%95%99%EC%8A%B5%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%99%98%EA%B2%BD-%EC%A4%80%EB%B9%84%EC%99%80-Hugging-Face-%EB%AA%A8%EB%8D%B8-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Wed, 22 Oct 2025 04:34:03 GMT</pubDate>
            <description><![CDATA[<h1 id="2장｜학습을-위한-환경-준비와-hugging-face-모델-불러오기">2장｜학습을 위한 환경 준비와 Hugging Face 모델 불러오기</h1>
<p>언어모델을 학습하거나 파인튜닝하려면 먼저 환경을 통일해야 한다.
모델을 불러오고, 데이터를 전처리하고, 학습 결과를 저장하는 과정은 모두 환경 설정에 좌우된다.
이 장에서는 <strong>로컬 환경에서 Ollama와 Hugging Face 모델을 함께 사용할 준비</strong>를 완료한다.</p>
<hr>
<h2 id="1-개발-환경-구성">1. 개발 환경 구성</h2>
<p>모델을 훈련하거나 튜닝할 때 반드시 맞춰야 하는 요소는 다음 네 가지다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Python 버전</strong></td>
<td>3.10 이상 권장. 3.8 이하에서는 일부 라이브러리 충돌 발생.</td>
</tr>
<tr>
<td><strong>CUDA / GPU</strong></td>
<td>NVIDIA GPU가 있으면 좋지만, CPU 학습도 가능. 단, 속도 차이는 10배 이상 발생.</td>
</tr>
<tr>
<td><strong>RAM/디스크</strong></td>
<td>최소 16 GB RAM, 모델과 데이터 저장용 20 GB 이상 여유 권장.</td>
</tr>
<tr>
<td><strong>가상환경</strong></td>
<td><code>venv</code>나 <code>conda</code>로 Python 환경을 독립시켜야 dependency 충돌을 막을 수 있다.</td>
</tr>
</tbody></table>
<h3 id="가상환경-생성">가상환경 생성</h3>
<pre><code class="language-bash">python3 -m venv venv
source venv/bin/activate     # Windows는 venv\Scripts\activate
pip install --upgrade pip</code></pre>
<hr>
<h2 id="2-필수-라이브러리-설치">2. 필수 라이브러리 설치</h2>
<p>이제 학습과 모델 불러오기를 위한 핵심 패키지를 설치한다.</p>
<pre><code class="language-bash">pip install transformers datasets peft accelerate torch torchvision torchaudio
pip install ollama</code></pre>
<p>설명:</p>
<ul>
<li><strong>transformers</strong> — Hugging Face 모델 및 토크나이저 관리.</li>
<li><strong>datasets</strong> — 데이터셋 다운로드 및 전처리.</li>
<li><strong>peft / accelerate</strong> — LoRA, QLoRA 등 파인튜닝 최적화용.</li>
<li><strong>ollama</strong> — 로컬에서 모델 관리 및 실행용 CLI + Python API.</li>
</ul>
<hr>
<h2 id="3-ollama-환경-확인">3. Ollama 환경 확인</h2>
<p>Ollama가 설치되어 있지 않다면 공식 스크립트를 이용한다.</p>
<pre><code class="language-bash">curl -fsSL https://ollama.com/install.sh | sh
ollama --version</code></pre>
<p>모델 목록을 확인한다.</p>
<pre><code class="language-bash">ollama list</code></pre>
<p>아직 아무 모델도 없을 것이다. 다음 단계에서 Hugging Face에서 모델을 불러와 Ollama에 통합할 것이다.</p>
<hr>
<h2 id="4-hugging-face-모델-가져오기">4. Hugging Face 모델 가져오기</h2>
<p>Ollama는 Hugging Face Hub의 모델을 직접 사용하지는 않지만,
파인튜닝에 사용할 <strong>사전학습(pretrained) 모델</strong>을 불러와 로컬 환경에서 변환할 수 있다.</p>
<h3 id="예시-bert-모델-불러오기">예시: BERT 모델 불러오기</h3>
<pre><code class="language-python">from transformers import AutoModelForSequenceClassification, AutoTokenizer

model_name = &quot;bert-base-uncased&quot;
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)

print(&quot;모델과 토크나이저 로드 완료:&quot;, model_name)</code></pre>
<p>이제 모델 파라미터 구조와 입력 토큰 형태를 확인할 수 있다.</p>
<pre><code class="language-python">text = &quot;Ollama fine-tuning with Hugging Face integration works perfectly.&quot;
tokens = tokenizer(text, return_tensors=&quot;pt&quot;)
outputs = model(**tokens)
print(outputs.logits)</code></pre>
<p>이 코드가 정상적으로 실행되면 Hugging Face 모델 사용 준비가 완료된 것이다.</p>
<hr>
<h2 id="5-모델-→-ollama-통합-준비">5. 모델 → Ollama 통합 준비</h2>
<p>Hugging Face 모델을 Ollama에 바로 학습시킬 순 없지만,
<strong>파인튜닝 후 내보내기(export)</strong> → <strong>Ollama용 모델 포맷으로 패키징</strong> 이라는 절차로 연결할 수 있다.</p>
<p>이를 위해서는 LoRA / QLoRA 형태의 가벼운 어댑터만 저장하고, Ollama가 지원하는 GGUF 또는 GGML 포맷으로 변환해야 한다.
다음 장에서 이 과정을 단계별로 다룰 예정이다.</p>
<p>예시 워크플로:</p>
<pre><code>Hugging Face 모델 (Transformers)
   ↓
파인튜닝 (LoRA, QLoRA)
   ↓
export → GGUF / GGML 형태
   ↓
ollama create -f Modelfile
   ↓
ollama run &lt;custom-model&gt;</code></pre><hr>
<h2 id="6-모델-다운로드-및-테스트-ollama-측">6. 모델 다운로드 및 테스트 (Ollama 측)</h2>
<p>Hugging Face 모델 실험 전, Ollama 환경이 정상적으로 작동하는지도 확인해야 한다.</p>
<pre><code class="language-bash">ollama pull llama3.2
ollama run llama3.2</code></pre>
<p>응답이 나오면 Ollama 측 환경도 정상 준비된 것이다.
이제 Hugging Face 모델과 Ollama 모델 양쪽 모두 실행할 수 있는 이중 환경이 완성되었다.</p>
<hr>
<h2 id="7-마무리">7. 마무리</h2>
<p>이번 2장에서는</p>
<ul>
<li>학습 및 튜닝을 위한 Python 환경 구축,</li>
<li>Hugging Face 모델 불러오기,</li>
<li>Ollama 환경 검증,</li>
<li>양쪽 환경의 연동 구조 설명까지 마쳤다.</li>
</ul>
<p>이제 다음 3장에서는 <strong>데이터 준비 및 전처리</strong>를 다룬다.
직접 CSV 또는 JSON 데이터를 읽어 토크나이징하고, 파인튜닝 입력 형식에 맞게 가공하는 과정을 진행할 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[1장 ｜ SLM & Ollama 입문]]></title>
            <link>https://velog.io/@hiio420_official/1%EC%9E%A5-%EF%BD%9C-SLM-Ollama-%EC%9E%85%EB%AC%B8</link>
            <guid>https://velog.io/@hiio420_official/1%EC%9E%A5-%EF%BD%9C-SLM-Ollama-%EC%9E%85%EB%AC%B8</guid>
            <pubDate>Tue, 21 Oct 2025 14:53:07 GMT</pubDate>
            <description><![CDATA[<h1 id="1장-｜-slm--ollama-입문">1장 ｜ SLM &amp; Ollama 입문</h1>
<p>이 글은 개발 초심자의 시선으로 작성된 기록이다. <strong>내 PC에서도 언어모델을 직접 돌려보겠다</strong>는 목표로 시작한다. 앞으로 이어질 장들에서는 환경 설정 → 모델 선택 → 데이터 준비 → 학습 및 튜닝 → 활용까지 순서대로 나아간다.</p>
<hr>
<h2 id="1-slmsmall-language-model이란-무엇인가">1. SLM(Small Language Model)이란 무엇인가</h2>
<p>언어모델 분야에서 “SLM”이라는 개념이 중요해지고 있다. 간단히 말하면, 대형 언어모델(LLM: Large Language Model)의 일부 특징을 유지하면서도 <strong>더 작고, 덜 무겁게, 덜 자원으로</strong> 실행될 수 있게 설계된 모델이다.</p>
<h3 id="특징">특징</h3>
<ul>
<li>파라미터(Parameter)의 수가 비교적 적다 → 모델 크기와 연산량이 적다. (<a href="https://www.mlsysbook.ai/contents/labs/raspi/llm/llm.html?utm_source=chatgpt.com" title="Small Language Models (SLM)">mlsysbook.ai</a>)</li>
<li>특정 도메인(예: 내부 보고서 요약, 코드 리뷰, FAQ 챗봇) 등에 맞춰 튜닝하기에 적합하다. (<a href="https://huggingface.co/blog/jjokah/small-language-model?utm_source=chatgpt.com" title="Small Language Models (SLM): A Comprehensive Overview">Hugging Face</a>)</li>
<li>리소스가 제한된 환경(내 PC, 노트북, 엣지 디바이스)에서도 실행 가능하다. (<a href="https://www.mlsysbook.ai/contents/labs/raspi/llm/llm.html?utm_source=chatgpt.com" title="Small Language Models (SLM)">mlsysbook.ai</a>)</li>
</ul>
<h3 id="한계">한계</h3>
<ul>
<li>범용적으로 질문 받고 답변하고 추론하는 데 있어 대형 모델만큼 깊이 있는 성능을 내기 어려울 수 있다. (<a href="https://medium.com/%40martinkeywood/small-language-models-the-rise-of-slms-are-tiny-lms-ready-for-prime-time-ac425b47c7f4?utm_source=chatgpt.com" title="Small Language Models: The Rise of SLMs — Are Tiny ...">Medium</a>)</li>
<li>문맥이 길거나 복잡한 추론을 요구하는 작업에서는 성능 저하가 나타날 수 있다. (<a href="https://www.mlsysbook.ai/contents/labs/raspi/llm/llm.html?utm_source=chatgpt.com" title="Small Language Models (SLM)">mlsysbook.ai</a>)</li>
</ul>
<h3 id="내가-slm을-선택한-이유">내가 SLM을 선택한 이유</h3>
<p>내 노트북(혹은 데스크탑)이 막연히 “언어모델을 돌릴 수 없다”는 한계에 머물러 있었기에, <strong>작고 효율적인 모델로 직접 경험해보자</strong>는 마음이 생겼다. 즉, “리소스 제약 → 경험 불가능”이라는 흐름을 끊고 싶었다.</p>
<hr>
<h2 id="2-ollama란-무엇인가">2. Ollama란 무엇인가</h2>
<p>모델만 확보한다고 끝나는 게 아니다. 다운로드하고 실행하고 관리할 수 있는 환경이 필요하다. 여기서 선택한 도구가 바로 <strong>Ollama</strong>이다.</p>
<h3 id="ollama의-특징">Ollama의 특징</h3>
<ul>
<li>로컬 환경(내 PC, 노트북)에서 언어모델을 다운로드하고 실행할 수 있다. (<a href="https://huggingface.co/blog/jjokah/small-language-model?utm_source=chatgpt.com" title="Small Language Models (SLM): A Comprehensive Overview">Hugging Face</a>)</li>
<li>커맨드라인(CLI)을 통해 모델을 제어할 수 있어 개발 흐름에 잘 맞는다. (<a href="https://thenewstack.io/how-to-get-started-running-small-language-models-at-the-edge/?utm_source=chatgpt.com" title="How To Get Started Running Small Language Models at ...">thenewstack.io</a>)</li>
<li>다양한 운영체제(macOS, Linux, Windows/WSL)를 지원하는 경우가 많아서 환경 제약이 크게 적다. (<a href="https://www.mlsysbook.ai/contents/labs/raspi/llm/llm.html?utm_source=chatgpt.com" title="Small Language Models (SLM)">mlsysbook.ai</a>)</li>
</ul>
<h3 id="이-시리즈에서-ollama를-선택한-이유">이 시리즈에서 Ollama를 선택한 이유</h3>
<p>내가 모델을 <strong>선택 → 다운로드 → 실행 → 활용</strong>하는 흐름을 경험하고 싶었고, Ollama는 이 흐름을 비교적 매끄럽게 만들어준다. 특히 주니어 입장에서 “설정이 어렵다→포기한다”가 되지 않게 만들고 싶었다.</p>
<hr>
<h2 id="3-이-시리즈에서-달성하고자-하는-것들">3. 이 시리즈에서 달성하고자 하는 것들</h2>
<p>앞으로 이 장 다음으로 이어질 글들을 통해 내가 이루고 싶은 목표다:</p>
<ul>
<li>내 PC 또는 노트북에서 <strong>SLM 모델을 직접 다운로드하고 실행하는 경험</strong>을 쌓는다.</li>
<li>준비한 데이터를 전처리하고, <strong>모델을 간단히 튜닝하거나 활용해본다</strong>.</li>
<li>그 결과물을 <strong>간단한 애플리케이션(예: 챗봇, 문서 요약 툴)</strong>으로 연결해본다.</li>
<li>메모리·저장공간·GPU여부 같은 “현실적 제약”을 고려하면서 실습을 한다.</li>
<li>또한 “로컬 언어모델”, “언어모델 튜닝”, “SLM 실습”, “Ollama 모델 사용법” 같은 키워드도 접하면서 블로그 글로서 검색 유입 가능성도 고려한다.</li>
</ul>
<hr>
<h2 id="4-따라하기-준비-체크리스트">4. 따라하기 준비 체크리스트</h2>
<p>다음 2장부터 바로 실습에 들어가야 하므로, 지금 이 순간 준비해야 할 항목들을 체크해보자.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>운영체제 확인</td>
<td>Windows(WSL 포함) 또는 macOS 또는 Linux 중 어떤 환경인지 미리 확인</td>
</tr>
<tr>
<td>메모리</td>
<td>최소 <strong>16GB</strong> 이상 권장 – 모델 크기/데이터 양에 따라 더 필요할 수 있다</td>
</tr>
<tr>
<td>저장공간</td>
<td>모델 파일 다운로드 + 데이터 저장 + 실행 결과 여유 공간 확보</td>
</tr>
<tr>
<td>GPU 여부</td>
<td>GPU가 있으면 속도가 빠르지만 없어도 CPU로 실습 가능하다는 점 기억</td>
</tr>
<tr>
<td>Ollama 설치</td>
<td>아래 설치 예제를 지금 실행해볼 수 있다</td>
</tr>
<tr>
<td>모델 다운로드 + 실행 테스트</td>
<td>모델 하나 다운로드해보고 실행해보는 흐름을 미리 시도해보자</td>
</tr>
</tbody></table>
<h3 id="ollama-설치-예제">Ollama 설치 예제</h3>
<p>아래는 리눅스 또는 맥 환경에서 실행 가능한 설치 예제 명령어이다.</p>
<pre><code class="language-bash">curl -fsSL https://ollama.com/install.sh | sh</code></pre>
<p>설치 후 터미널에서 아래 명령어로 설치가 성공했는지 확인한다.</p>
<pre><code class="language-bash">ollama --version</code></pre>
<h3 id="모델-다운로드-및-실행-예제">모델 다운로드 및 실행 예제</h3>
<p>예를 들어 “smollm:1.7b” 같은 SLM 모델을 다운로드하고 실행해볼 수 있다.</p>
<pre><code class="language-bash">ollama pull smollm:1.7b
ollama run smollm:1.7b</code></pre>
<p>실행 후 터미널이 입력 대기 상태가 되면 예를 들어 다음과 같은 질문을 입력해본다.</p>
<pre><code>Explain what small language models are and how they differ from large language models.</code></pre><p>정상적으로 답변이 나오면 준비 완료다!</p>
<hr>
<h2 id="5-마무리">5. 마무리</h2>
<p>이제 첫 장을 통해 <strong>왜 SLM이 중요한가</strong>, <strong>왜 Ollama인가</strong>, 그리고 <strong>내가 이 시리즈로 무엇을 이루려 하는가</strong>를 정리했다. 다음 장에서는 <strong>환경 설정 + 모델 선택</strong>으로 더 깊이 들어가겠다. 어떤 모델을 선택해야 할지 분석하고, 나도 선택 기준을 세워보겠다.</p>
<p>다음 장에서 뵙자.</p>
<hr>
<p>SLM, SmallLanguageModel, Ollama, 로컬LLM, 모델튜닝, 언어모델, AI개발, 개발블로그, 머신러닝, 모델학습</p>
<p>출처</p>
<ul>
<li>“Small Language Models (SLM): A Comprehensive Overview.” – Hugging Face Blog. (<a href="https://huggingface.co/blog/jjokah/small-language-model?utm_source=chatgpt.com" title="Small Language Models (SLM): A Comprehensive Overview">Hugging Face</a>)</li>
<li>“How to get started running Small Language Models at the edge.” – The New Stack. (<a href="https://thenewstack.io/how-to-get-started-running-small-language-models-at-the-edge/?utm_source=chatgpt.com" title="How To Get Started Running Small Language Models at ...">thenewstack.io</a>)</li>
<li>“Small Language Models: The Rise of SLMs — Are Tiny LMs Ready for Prime Time?” – Medium. (<a href="https://medium.com/%40martinkeywood/small-language-models-the-rise-of-slms-are-tiny-lms-ready-for-prime-time-ac425b47c7f4?utm_source=chatgpt.com" title="Small Language Models: The Rise of SLMs — Are Tiny ...">Medium</a>)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[
# BFF(Backend for Frontend) 아키텍처 완벽 가이드]]></title>
            <link>https://velog.io/@hiio420_official/BFFBackend-for-Frontend-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@hiio420_official/BFFBackend-for-Frontend-%EC%95%84%ED%82%A4%ED%85%8D%EC%B2%98-%EC%99%84%EB%B2%BD-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Tue, 09 Sep 2025 05:51:53 GMT</pubDate>
            <description><![CDATA[<h1 id="bffbackend-for-frontend-아키텍처-완벽-가이드">BFF(Backend for Frontend) 아키텍처 완벽 가이드</h1>
<h2 id="1-bff란-무엇인가">1. BFF란 무엇인가?</h2>
<p>BFF(Backend for Frontend)는 이름 그대로 <strong>특정 프론트엔드 애플리케이션에 최적화된 백엔드 레이어</strong>입니다.
즉, “하나의 거대한 API 게이트웨이” 대신 <strong>각 클라이언트(웹, 모바일, IoT 등)에 맞춤형 백엔드</strong>를 두어 API를 설계하는 방식입니다.</p>
<ul>
<li>웹 프론트엔드 → Web BFF</li>
<li>모바일 앱 → Mobile BFF</li>
<li>관리자 페이지 → Admin BFF</li>
</ul>
<p>이런 식으로 나누어지며, 클라이언트의 특성과 화면 단위 요청을 고려해 데이터 가공과 최적화를 처리합니다.</p>
<hr>
<h2 id="2-왜-bff가-필요한가">2. 왜 BFF가 필요한가?</h2>
<p>기존 구조(단일 API Gateway)에서는 이런 문제가 생깁니다:</p>
<ol>
<li><strong>과도한 데이터</strong>: 모바일 앱에는 필요 없는 필드까지 내려줌.</li>
<li><strong>빈번한 API 호출</strong>: 화면 하나를 렌더링하기 위해 3~4개의 API를 호출해야 함.</li>
<li><strong>개발 속도 저하</strong>: 프론트엔드 요구사항이 많아질수록 백엔드 수정이 잦음.</li>
</ol>
<p>👉 이를 해결하기 위해 BFF는 **“클라이언트 친화적 API 레이어”**로 동작합니다.</p>
<hr>
<h2 id="3-bff-아키텍처-구조">3. BFF 아키텍처 구조</h2>
<pre><code class="language-text">[웹 브라우저] ──▶ [Web BFF] ──▶ [Microservices]
[모바일 앱]   ──▶ [Mobile BFF] ──▶ [Microservices]</code></pre>
<ul>
<li>각 BFF는 <strong>특정 프론트엔드의 UX 요구사항</strong>에 맞춰 API를 조합/가공합니다.</li>
<li>내부적으로는 동일한 마이크로서비스들을 호출하지만, <strong>출력 형식과 호출 횟수 최적화</strong>가 다릅니다.</li>
</ul>
<hr>
<h2 id="4-bff의-장점">4. BFF의 장점</h2>
<p>✅ <strong>프론트엔드 맞춤 최적화</strong>
: 화면 1개 → API 1번 호출 가능 (Overfetching/Underfetching 최소화).</p>
<p>✅ <strong>개발 효율성 증가</strong>
: 프론트엔드 팀이 독립적으로 BFF 레이어를 관리 가능.</p>
<p>✅ <strong>보안 &amp; 정책 분리</strong>
: Admin BFF에는 관리자 권한 정책을 추가하고, Mobile BFF에는 트래픽 최적화를 적용.</p>
<hr>
<h2 id="5-bff의-단점">5. BFF의 단점</h2>
<p>⚠️ 서비스가 늘어나면 BFF도 많아져 <strong>운영 복잡성 증가</strong>.
⚠️ 동일한 로직이 여러 BFF에 중복될 수 있음.</p>
<p>👉 따라서, <strong>공통 로직은 마이크로서비스에서 관리</strong>하고,
<strong>표현 최적화는 BFF에서 처리</strong>하는 구조가 적합합니다.</p>
<hr>
<h2 id="6-실무-적용-예시">6. 실무 적용 예시</h2>
<ol>
<li><strong>NestJS로 Web BFF 구축</strong></li>
</ol>
<pre><code class="language-ts">@Controller(&#39;dashboard&#39;)
export class DashboardBFFController {
  constructor(
    private readonly orderService: OrderService,
    private readonly userService: UserService
  ) {}

  @Get()
  async getDashboardData() {
    const orders = await this.orderService.getRecentOrders();
    const user = await this.userService.getProfile();
    return { orders, user };
  }
}</code></pre>
<p>→ 프론트엔드 대시보드 화면에서 필요한 데이터만 조합해 반환.</p>
<ol start="2">
<li><strong>Mobile BFF</strong>에서는 같은 데이터를 더 경량화하여 내려줄 수 있음.</li>
</ol>
<hr>
<h2 id="7-bff-선택-가이드">7. BFF 선택 가이드</h2>
<table>
<thead>
<tr>
<th>상황</th>
<th>선택</th>
</tr>
</thead>
<tbody><tr>
<td>단일 웹서비스</td>
<td>굳이 BFF 필요 없음, API Gateway로 충분</td>
</tr>
<tr>
<td>다중 클라이언트(웹/앱/IoT)</td>
<td>BFF 적극 도입</td>
</tr>
<tr>
<td>프론트엔드 요구사항 변화 잦음</td>
<td>BFF 유리</td>
</tr>
<tr>
<td>팀 규모 작고 단순 서비스</td>
<td>오히려 관리 비용 커질 수 있음</td>
</tr>
</tbody></table>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[# Docker Network 완벽 이해: Bridge, Host, Overlay의 차이와 활용법]]></title>
            <link>https://velog.io/@hiio420_official/Docker-Network-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4-Bridge-Host-Overlay%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%99%80-%ED%99%9C%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@hiio420_official/Docker-Network-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4-Bridge-Host-Overlay%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%99%80-%ED%99%9C%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 09 Sep 2025 03:13:14 GMT</pubDate>
            <description><![CDATA[<h1 id="docker-network-완벽-이해-bridge-host-overlay의-차이와-활용법">Docker Network 완벽 이해: Bridge, Host, Overlay의 차이와 활용법</h1>
<p>Docker를 활용해 애플리케이션을 운영하다 보면 반드시 마주치는 개념이 **네트워크(Network)**입니다.
특히 컨테이너 간 통신을 어떻게 구성할지에 따라 서비스 구조가 달라지기 때문에, <code>bridge</code>, <code>host</code>, <code>overlay</code> 같은 네트워크 드라이버의 차이를 명확히 이해하는 것이 중요합니다.</p>
<p>이번 글에서는 단순한 정의를 넘어, <strong>실무에서 어떤 상황에서 어떤 네트워크를 선택해야 하는지</strong>를 예시와 함께 정리해보겠습니다.</p>
<hr>
<h2 id="1-docker-network-기본-개념">1. Docker Network 기본 개념</h2>
<p>Docker 컨테이너는 기본적으로 <strong>격리된 환경</strong>에서 실행됩니다.
네트워크가 없다면, 각 컨테이너는 마치 인터넷이 끊긴 독립 PC처럼 동작하게 됩니다.</p>
<p>따라서 Docker는 다양한 **네트워크 드라이버(driver)**를 제공하여 컨테이너 간 통신, 외부 연결, 포트 매핑 등을 지원합니다.</p>
<p>확인 방법은 다음과 같습니다:</p>
<pre><code class="language-bash">docker network ls</code></pre>
<p>출력 예시:</p>
<pre><code>NETWORK ID     NAME      DRIVER    SCOPE
a12b3c4d5e6f   bridge    bridge    local
f23g4h5i6j7k   host      host      local
g34h5i6j7k8l   none      null      local</code></pre><hr>
<h2 id="2-bridge-네트워크-기본값">2. Bridge 네트워크 (기본값)</h2>
<ul>
<li><p><strong>정의</strong>: 컨테이너끼리 연결되는 <strong>가상 네트워크 스위치</strong> 역할.</p>
</li>
<li><p><strong>특징</strong>:</p>
<ul>
<li>같은 bridge 네트워크에 속한 컨테이너끼리만 이름(DNS) 기반 통신 가능.</li>
<li>외부 통신은 <code>-p</code> 옵션으로 포트를 노출해야 가능.</li>
</ul>
</li>
<li><p><strong>실습 예시</strong>:</p>
</li>
</ul>
<pre><code class="language-bash"># 새 브리지 네트워크 생성
docker network create my-bridge

# 같은 네트워크에 두 개의 컨테이너 연결
docker run -d --name web1 --network my-bridge nginx
docker run -d --name web2 --network my-bridge nginx

# 컨테이너끼리 ping 가능
docker exec -it web1 ping web2</code></pre>
<ul>
<li><p><strong>사용 사례</strong>:</p>
<ul>
<li>단일 서버에서 여러 개의 컨테이너가 서비스할 때 (예: 웹 + DB).</li>
<li>네임 기반 접근이 가능해 로컬 개발 환경에 적합.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="3-host-네트워크">3. Host 네트워크</h2>
<ul>
<li><p><strong>정의</strong>: 컨테이너가 <strong>호스트와 네트워크 스택을 공유</strong>.</p>
</li>
<li><p><strong>특징</strong>:</p>
<ul>
<li>포트 매핑(<code>-p</code>) 불필요 → 호스트와 동일한 네트워크 인터페이스 사용.</li>
<li>오버헤드가 적고 성능이 뛰어남.</li>
<li>하지만 컨테이너 간 네트워크 격리가 없음 → 보안 이슈 가능.</li>
</ul>
</li>
<li><p><strong>실습 예시</strong>:</p>
</li>
</ul>
<pre><code class="language-bash">docker run -d --name web --network host nginx</code></pre>
<p>→ 이제 <code>http://localhost:80</code> 으로 바로 접속 가능 (포트 매핑 불필요).</p>
<ul>
<li><p><strong>사용 사례</strong>:</p>
<ul>
<li>성능 최적화가 중요한 고성능 애플리케이션.</li>
<li>Prometheus, ELK 같은 모니터링/로깅 툴.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-overlay-네트워크">4. Overlay 네트워크</h2>
<ul>
<li><p><strong>정의</strong>: 여러 Docker Host 간 네트워크를 연결해주는 <strong>분산 네트워크</strong>.</p>
</li>
<li><p><strong>특징</strong>:</p>
<ul>
<li>Swarm 또는 Kubernetes 같은 오케스트레이션 환경에서 사용.</li>
<li>서로 다른 물리 서버의 컨테이너끼리 같은 네트워크처럼 동작.</li>
</ul>
</li>
<li><p><strong>실습 예시 (Swarm 모드)</strong>:</p>
</li>
</ul>
<pre><code class="language-bash"># Swarm 초기화
docker swarm init

# Overlay 네트워크 생성
docker network create -d overlay my-overlay

# Service 실행
docker service create --name web --network my-overlay nginx</code></pre>
<ul>
<li><p><strong>사용 사례</strong>:</p>
<ul>
<li>멀티 노드 환경에서 마이크로서비스 아키텍처.</li>
<li>분산 시스템 구축.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="5-네트워크-선택-가이드">5. 네트워크 선택 가이드</h2>
<table>
<thead>
<tr>
<th>네트워크 유형</th>
<th>장점</th>
<th>단점</th>
<th>활용 사례</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Bridge</strong></td>
<td>컨테이너 간 DNS 지원, 격리</td>
<td>포트 매핑 필요</td>
<td>로컬 개발, 단일 서버</td>
</tr>
<tr>
<td><strong>Host</strong></td>
<td>빠른 성능, 설정 단순</td>
<td>보안 취약, 포트 충돌</td>
<td>모니터링, 고성능 서버</td>
</tr>
<tr>
<td><strong>Overlay</strong></td>
<td>멀티 호스트 지원, 분산</td>
<td>설정 복잡, Swarm 필요</td>
<td>마이크로서비스, 클러스터</td>
</tr>
</tbody></table>
<hr>
<h2 id="💡-정리">💡 정리</h2>
<ul>
<li>단일 서버 환경 → <strong>Bridge</strong></li>
<li>성능 우선, 네트워크 단순화 → <strong>Host</strong></li>
<li>멀티 노드/클러스터 환경 → <strong>Overlay</strong></li>
</ul>
<p>실무에서는 <strong>개발 단계는 bridge → 운영 단계는 host/overlay</strong>로 전환하는 경우가 많습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2025년 7월 마지막주]]></title>
            <link>https://velog.io/@hiio420_official/%ED%9A%8C%EA%B3%A0-2025%EB%85%84-7%EC%9B%94-%EB%A7%88%EC%A7%80%EB%A7%89%EC%A3%BC</link>
            <guid>https://velog.io/@hiio420_official/%ED%9A%8C%EA%B3%A0-2025%EB%85%84-7%EC%9B%94-%EB%A7%88%EC%A7%80%EB%A7%89%EC%A3%BC</guid>
            <pubDate>Thu, 31 Jul 2025 14:05:13 GMT</pubDate>
            <description><![CDATA[<p>7월 마지막날 31일
더운 날씨
월요일에 에어컨에서 밖으로 물을 내보내는 호스가 중간에 끊겨서 가동시킬 수 없는 상황이 와버렸다.
그래서 부랴부랴 예약을 했지만 목요일에 방문이 가능하다고 해서, 오늘 오후에 고쳤다.
너무 더운 방에서 잠을 푹 잘 수 없어서 그런지 계속 피곤한 기분이였다.
에어컨 너무 중요하군.
더운 방안에서 PC까지 켜져 있으니 더위가 가시질 않아 개발 공부를 하지 싫어져 버렸다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[git fetch, git rebase, git pull 차이와 실전 사용법]]></title>
            <link>https://velog.io/@hiio420_official/git-fetch-git-rebase-git-pull-%EC%B0%A8%EC%9D%B4%EC%99%80-%EC%8B%A4%EC%A0%84-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@hiio420_official/git-fetch-git-rebase-git-pull-%EC%B0%A8%EC%9D%B4%EC%99%80-%EC%8B%A4%EC%A0%84-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 08 Jul 2025 01:52:10 GMT</pubDate>
            <description><![CDATA[<h1 id="git-fetch-git-rebase-git-pull-차이와-실전-사용법"><code>git fetch</code>, <code>git rebase</code>, <code>git pull</code> 차이와 실전 사용법</h1>
<hr>
<h2 id="1--git-fetch--최신-정보를-가져오기만-해">1.  <code>git fetch</code> – *&quot;최신 정보를 가져오기만 해!&quot;*</h2>
<pre><code class="language-bash">git fetch origin</code></pre>
<ul>
<li><strong>원격 저장소의 변경사항을 가져오되</strong>, <strong>내 로컬 브랜치에는 적용하지 않음</strong></li>
<li>단지 <code>.git</code> 디렉토리 내부의 정보를 최신화</li>
<li><code>git log origin/main</code> 혹은 <code>git diff origin/main</code> 등으로 확인 가능</li>
</ul>
<h3 id="언제-쓰냐">언제 쓰냐?</h3>
<ul>
<li><strong>변경사항을 확인하고 싶지만, 아직 내 코드에 적용하고 싶지 않을 때</strong></li>
<li>특히 <strong>CI/CD로 충돌을 미리 체크하고 싶을 때</strong> 아주 유용</li>
</ul>
<hr>
<h2 id="2--git-rebase--내-작업을-최신-커밋-위에-깔끔하게-다시-올려줘">2.  <code>git rebase</code> – *&quot;내 작업을 최신 커밋 위에 깔끔하게 다시 올려줘&quot;*</h2>
<pre><code class="language-bash">git rebase origin/main</code></pre>
<ul>
<li><strong>현재 브랜치에 있는 커밋을, 원격 브랜치의 최신 커밋 이후로 옮김</strong></li>
<li>즉, <strong>내가 한 작업을 나중에 한 것처럼 만들기</strong> (히스토리가 깨끗해짐)</li>
<li>충돌이 날 수 있지만, 충돌을 직접 해결할 수 있어서 협업에 적합</li>
</ul>
<h3 id="언제-쓰냐-1">언제 쓰냐?</h3>
<ul>
<li>팀에서 <code>rebase</code> 전략을 쓰는 경우</li>
<li>커밋 로그를 깔끔하게 유지하고 싶을 때</li>
</ul>
<h3 id="주의">주의!</h3>
<ul>
<li><code>rebase</code>는 <strong>공유된 브랜치에서는 신중히!</strong>
<code>git push --force</code>가 필요할 수 있음. 잘못 쓰면 동료의 작업이 날아갈 수 있음.</li>
</ul>
<hr>
<h2 id="3--git-pull--최신-걸-가져오고-바로-병합해줘">3.  <code>git pull</code> – *&quot;최신 걸 가져오고 바로 병합해줘&quot;*</h2>
<pre><code class="language-bash">git pull origin main</code></pre>
<ul>
<li><code>git fetch</code> + <code>git merge</code>를 한 번에 수행</li>
<li><strong>가장 많이 쓰이지만, 커밋 히스토리가 지저분해질 수 있음</strong></li>
<li>충돌도 발생 가능</li>
</ul>
<h3 id="🔧-언제-쓰냐">🔧 언제 쓰냐?</h3>
<ul>
<li>혼자 작업할 때 빠르게 최신화하려면 편함</li>
<li>그러나 팀 협업에서는 **의도치 않은 병합 커밋(Merge commit)**이 생겨 버그 추적이 힘들 수 있음</li>
</ul>
<hr>
<h2 id="실전-예시">실전 예시</h2>
<h3 id="협업-중이라면">협업 중이라면?</h3>
<pre><code class="language-bash">git fetch origin
git rebase origin/main</code></pre>
<blockquote>
<p>✔️ 충돌을 내가 직접 해결하면서 커밋 히스토리를 깔끔하게 유지할 수 있음</p>
</blockquote>
<hr>
<h3 id="그냥-빠르게-동기화하고-싶다면">그냥 빠르게 동기화하고 싶다면?</h3>
<pre><code class="language-bash">git pull origin main</code></pre>
<blockquote>
<p>✔️ 단, 히스토리 관리에 민감하지 않을 때만</p>
</blockquote>
<hr>
<h2 id="추가-꿀팁">추가 꿀팁</h2>
<h3 id="차이점-비교-요약">차이점 비교 요약</h3>
<table>
<thead>
<tr>
<th>명령어</th>
<th>원격 변경사항 반영</th>
<th>로컬 히스토리 변경</th>
<th>충돌 해결 방식</th>
</tr>
</thead>
<tbody><tr>
<td><code>git fetch</code></td>
<td>❌ (보기만 함)</td>
<td>❌</td>
<td>없음</td>
</tr>
<tr>
<td><code>git rebase</code></td>
<td>✅</td>
<td>✅ (히스토리 수정)</td>
<td>직접 해결 필요</td>
</tr>
<tr>
<td><code>git pull</code></td>
<td>✅</td>
<td>✅ (병합)</td>
<td>자동 or 수동 병합</td>
</tr>
</tbody></table>
<hr>
<h1 id="마무리하며">마무리하며</h1>
<p>예전엔 <code>git pull</code>만 써도 충분했지만,
협업이 잦아지고 충돌/히스토리 문제가 늘어나면서
<strong><code>fetch + rebase</code> 조합을 더 많이 쓰게 되더라구요.</strong></p>
<p>실제로 큰 서비스팀에서 일할수록, 커밋 하나하나가 버그 추적의 단서가 되기 때문에
<strong><code>rebase</code>를 쓰는 게 디폴트인 팀도 많아요.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 7. Guards]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-7.-Guards</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-7.-Guards</guid>
            <pubDate>Fri, 13 Jun 2025 08:34:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/be99743c-2f25-4e35-aa5a-c214fe86201f/image.png" alt="">
가드는 인터페이스를 구현하는 데코레이터 @Injectable()로 주석이 달린 클래스입니다.</p>
<p>가드는 단일 책임을 갖습니다 . 런타임에 존재하는 특정 조건(권한, 역할, ACL 등)에 따라 주어진 요청을 경로 처리기가 처리할지 여부를 결정합니다. 이를 권한 부여(authorization)라고 합니다 . 권한 부여(authorization)와 그 사촌격인 인증 (authentication)은 기존 Express 애플리케이션에서 일반적으로 미들웨어 가 처리해 왔습니다 . 토큰 검증이나 객체에 속성 연결과 같은 작업은 request특정 경로 컨텍스트(및 해당 메타데이터)와 긴밀하게 연결되지 않기 때문에 미들웨어는 인증에 적합한 선택입니다.</p>
<p> next()함수를 호출한 후 어떤 핸들러가 실행될지 알 수 없습니다. 반면, 가드는 인스턴스 에 접근하여 ExecutionContext다음에 무엇이 실행될지 정확히 알고 있습니다. 가드는 예외 필터, 파이프, 인터셉터와 마찬가지로 요청/응답 주기의 정확한 시점에 처리 로직을 삽입할 수 있도록 설계되었으며, 이를 선언적으로 수행할 수 있습니다. 이는 코드를 DRY하고 선언적으로 유지하는 데 도움이 됩니다.</p>
<hr>
<p><strong>Q1. NestJS의 Guard란 무엇인가요? 언제 사용하나요?</strong></p>
<p><strong>A1.</strong>
Guard는 NestJS에서 요청(Request)을 컨트롤러에 전달하기 전에 실행되는 클래스입니다.
주로 인증(Authentication) 또는 권한(Authorization)과 같은 조건을 검증할 때 사용됩니다.
예를 들어, 사용자가 특정 API에 접근 가능한지를 판단할 때 Guard를 설정하면, 조건이 맞지 않으면 요청 자체를 차단할 수 있어요.</p>
<hr>
<p><strong>Q2. Guard를 구현할 때 어떤 인터페이스와 메서드를 사용하나요?</strong></p>
<p><strong>A2.</strong>
Guard는 <code>@Injectable()</code> 데코레이터가 붙은 클래스이며, <code>CanActivate</code> 인터페이스를 구현해야 합니다.
핵심은 <code>canActivate(context: ExecutionContext): boolean | Promise&lt;boolean&gt;</code> 메서드입니다.
이 메서드가 true를 반환하면 요청이 통과되고, false를 반환하면 요청이 거부됩니다.</p>
<hr>
<p><strong>Q3. <code>ExecutionContext</code>는 어떤 역할을 하나요?</strong></p>
<p><strong>A3.</strong>
<code>ExecutionContext</code>는 현재 실행 중인 요청의 정보를 추상화한 객체입니다.
<code>context.switchToHttp().getRequest()</code>를 통해 HTTP 요청 객체에 접근할 수 있어요.
RPC, GraphQL, WebSocket 등 다양한 플랫폼에서도 사용할 수 있도록 추상화되어 있어 유연성이 큽니다.</p>
<hr>
<p><strong>Q4. Guard와 Interceptor, Middleware의 차이는 무엇인가요?</strong></p>
<p><strong>A4.</strong>
이 셋은 모두 요청 처리 흐름에서 관여하지만 시점과 목적이 달라요.</p>
<ul>
<li><strong>Middleware</strong>: 요청이 애플리케이션에 들어오기 전에 실행. 주로 로깅, 요청 변형, CORS 처리 등.</li>
<li><strong>Guard</strong>: 요청이 컨트롤러에 도달하기 직전에 실행. 인증/인가 로직에 최적화.</li>
<li><strong>Interceptor</strong>: 컨트롤러 실행 전/후에 실행. 응답 가공, 캐싱, 트랜잭션 처리 등에 사용.</li>
</ul>
<hr>
<p><strong>Q5. Guard는 어디에 적용할 수 있나요?</strong></p>
<p><strong>A5.</strong>
Guard는 다음과 같이 다양한 범위에 적용할 수 있어요.</p>
<ul>
<li>메서드 단위: <code>@UseGuards(MyGuard)</code></li>
<li>컨트롤러 전체: <code>@UseGuards(MyGuard)</code>를 컨트롤러 클래스에</li>
<li>전역 적용: <code>app.useGlobalGuards()</code>를 <code>main.ts</code>에서 설정</li>
</ul>
<hr>
<p><strong>Q6. Role 기반 접근 제어는 어떻게 구현하나요?</strong></p>
<p><strong>A6.</strong>
주로 커스텀 데코레이터 <code>@Roles()</code>와 <code>RolesGuard</code>를 조합해서 사용합니다.</p>
<ol>
<li><code>@Roles()</code> 데코레이터로 필요한 역할을 지정하고</li>
<li><code>RolesGuard</code>에서 사용자의 역할이 그에 포함되는지를 검사하는 방식입니다.</li>
</ol>
<p>이런 구조는 NestJS의 메타데이터(<code>Reflector</code>)와 Guard의 궁합을 잘 보여주는 예시죠.</p>
<hr>
<p><strong>Q7. NestJS Guard를 사용하면서 겪은 어려움이나 깨달음이 있다면?</strong></p>
<p><strong>A7.</strong>
처음에는 <code>ExecutionContext</code> 구조나 HTTP 외의 컨텍스트 전환 방식이 헷갈릴 수 있었어요.
하지만 구조를 하나하나 뜯어보고 나면 NestJS의 확장성과 철학이 잘 드러나는 부분이기도 합니다.
특히 인증 시스템과 역할 기반 인가를 설계할 때 Guard는 핵심 역할을 하기 때문에, 실제 프로젝트에서도 Guard의 잘 설계된 구조는 시스템 안정성과 보안성에 큰 도움이 되었습니다.</p>
<hr>
<h1 id="권한-부여-가드">권한 부여 가드</h1>
<p>권한 부여는 Guards의 훌륭한 사용 사례입니다. 특정 경로는 호출자(일반적으로 특정 인증된 사용자)가 충분한 권한을 가지고 있을 때만 사용할 수 있어야 하기 때문입니다. AuthGuard이제 만들 는 인증된 사용자(따라서 요청 헤더에 토큰이 첨부됨)를 가정합니다. 토큰을 추출하고 유효성을 검사한 후, 추출된 정보를 사용하여 요청 진행 여부를 결정합니다.</p>
<pre><code class="language-ts">
import { Injectable, CanActivate, ExecutionContext } from &#39;@nestjs/common&#39;;
import { Observable } from &#39;rxjs&#39;;

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise&lt;boolean&gt; | Observable&lt;boolean&gt; {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

</code></pre>
<h1 id="역할-기반-인증">역할 기반 인증</h1>
<p>특정 역할을 가진 사용자에게만 접근을 허용하는 더욱 기능적인 가드를 만들어 보겠습니다. 기본 가드 템플릿부터 시작하여 다음 섹션에서 이를 기반으로 확장해 보겠습니다. 지금은 모든 요청이 진행되도록 허용합니다.</p>
<pre><code class="language-ts">
import { Injectable, CanActivate, ExecutionContext } from &#39;@nestjs/common&#39;;
import { Observable } from &#39;rxjs&#39;;

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise&lt;boolean&gt; | Observable&lt;boolean&gt; {
    return true;
  }
}

</code></pre>
<h1 id="바인딩-가드">바인딩 가드</h1>
<p>파이프 및 예외 필터처럼 가드는 컨트롤러 범위 , 메서드 범위 또는 전역 범위일 수 있습니다. 아래에서는 @UseGuards()데코레이터를 사용하여 컨트롤러 범위 가드를 설정합니다. 이 데코레이터는 단일 인수 또는 쉼표로 구분된 인수 목록을 받을 수 있습니다. 이를 통해 하나의 선언으로 적절한 가드 세트를 쉽게 적용할 수 있습니다.</p>
<pre><code class="language-ts">
@Controller(&#39;cats&#39;)
@UseGuards(RolesGuard)
export class CatsController {}
</code></pre>
<pre><code class="language-ts">
@Controller(&#39;cats&#39;)
@UseGuards(new RolesGuard())
export class CatsController {}

</code></pre>
<pre><code class="language-ts">
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new RolesGuard());
</code></pre>
<pre><code class="language-ts">
import { Module } from &#39;@nestjs/common&#39;;
import { APP_GUARD } from &#39;@nestjs/core&#39;;

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}
</code></pre>
<h1 id="핸들러당-역할-설정">핸들러당 역할 설정</h1>
<p> RolesGuard시스템은 잘 작동하지만, 아직 그다지 똑똑하지는 않습니다. 가장 중요한 가드 기능인 실행 컨텍스트 를 아직 활용하지 못하고 있습니다 . 아직 역할, 즉 각 핸들러에 허용되는 역할에 대해 알지 못합니다.예를 들어 CatsController, 는 경로마다 다른 권한 체계를 가질 수 있습니다. 어떤 경로는 관리자에게만 제공되고, 어떤 경로는 모든 사용자에게 공개될 수 있습니다. 유연하고 재사용 가능한 방식으로 역할과 경로를 어떻게 일치시킬 수 있을까요?</p>
<p>여기서 사용자 지정 메타데이터가 중요한 역할을 합니다. Nest는 정적 메서드를 통해 생성된 데코레이터 또는 기본 제공 데코레이터를 통해 경로 핸들러에 사용자 지정 메타데이터를 첨부할 수 있는 기능을 제공합니다</p>
<p>예를 들어, 메타데이터를 핸들러에 첨부하는 메서드를 @Roles()사용하여 데코레이터를 만들어 보겠습니다 . 이는 프레임워크에서 기본적으로 제공되며 패키지에서 노출됩니다.</p>
<pre><code class="language-ts">
import { Reflector } from &#39;@nestjs/core&#39;;

export const Roles = Reflector.createDecorator&lt;string[]&gt;();

</code></pre>
<pre><code class="language-ts">
@Post()
@Roles([&#39;admin&#39;])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
</code></pre>
<pre><code class="language-ts">


import { Injectable, CanActivate, ExecutionContext } from &#39;@nestjs/common&#39;;
import { Reflector } from &#39;@nestjs/core&#39;;
import { Roles } from &#39;./roles.decorator&#39;;

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const roles = this.reflector.get(Roles, context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 6. Exception Filter]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-6.-Exception-Filter</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-6.-Exception-Filter</guid>
            <pubDate>Tue, 10 Jun 2025 04:47:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/b0b4bdef-a576-455a-9958-c9c5f4a0178c/image.png" alt="">
Nest에는 애플리케이션 전체에서 처리되지 않은 모든 예외를 처리하는 내장 예외 계층이 있습니다. 애플리케이션 코드에서 예외를 처리하지 못하면 이 계층에서 해당 예외를 포착하여 사용자에게 친숙한 적절한 응답을 자동으로 전송합니다.</p>
<p>기본적으로 이 작업은 내장된 전역 예외 필터 에 의해 수행되며 , 이 필터는 유형 HttpException(및 해당 하위 클래스)의 예외를 처리합니다. 예외가 인식되지 않는HttpException 경우( 또는 를 상속하는 클래스가 아닌 경우 HttpException), 내장된 예외 필터는 다음과 같은 기본 JSON 응답을 생성합니다.</p>
<h1 id="표준-예외-발생">표준 예외 발생</h1>
<p>Nest는 패키지 HttpException에 내장된 클래스를 제공합니다 @nestjs/common. 일반적인 HTTP REST/GraphQL API 기반 애플리케이션의 경우, 특정 오류 발생 시 표준 HTTP 응답 객체를 전송하는 것이 가장 좋습니다.</p>
<p>예를 들어, 에 메서드( 라우트 핸들러) CatsController가 있습니다 . 이 라우트 핸들러가 어떤 이유로든 예외를 발생시킨다고 가정해 보겠습니다. 이를 보여주기 위해 다음과 같이 하드코딩합니다.</p>
<pre><code class="language-typescript">
// cats.controller.ts

import { Controller, Get, Param, NotFoundException } from &#39;@nestjs/common&#39;;
import { CatsService } from &#39;./cats.service&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  constructor(private readonly catsService: CatsService) {}

  @Get(&#39;:id&#39;)
  async findOne(@Param(&#39;id&#39;) id: string) {
    const cat = await this.catsService.findById(id);
    if (!cat) {
      throw new NotFoundException(`고양이 ID ${id}를 찾을 수 없습니다.`);
    }
    return cat;
  }
}
</code></pre>
<p>생성자 HttpException는 응답을 결정하는 두 가지 필수 인수를 사용합니다.</p>
<ol>
<li>response는 JSON 응답 본문을 정의합니다. 아래 설명된 대로 string 또는 object 일 수 있습니다.</li>
<li>HTTP 상태 코드를status 정의합니다 .</li>
</ol>
<p>기본적으로 JSON 응답 본문에는 두 가지 속성이 포함됩니다.</p>
<ol>
<li>statusCodestatus: 인수 에 제공된 HTTP 상태 코드를 기본값으로 사용합니다.</li>
<li>message: HTTP 오류에 대한 간략한 설명status
JSON 응답 본문의 메시지 부분만 재정의하려면 response인수에 문자열을 제공하세요. JSON 응답 본문 전체를 재정의하려면 response인수에 객체를 전달하세요. Nest는 객체를 직렬화하여 JSON 응답 본문으로 반환합니다.</li>
</ol>
<p>두 번째 생성자 인수 - status는 유효한 HTTP 상태 코드여야 합니다. .@nestjs/common HttpStatus에서 가져온 열거형을 사용하는 것이 가장 좋습니다.</p>
<p>세 번째 생성자 인수(선택 사항) 인 options-HttpException 는 오류 원인을 제공하는 데 사용할 수 있습니다 . 이 cause객체는 응답 객체로 직렬화되지 않지만, 로깅 목적으로 유용할 수 있으며, 오류 발생의 원인이 된 내부 오류에 대한 중요한 정보를 제공합니다 .</p>
<pre><code class="language-ts">import { HttpException, HttpStatus } from &#39;@nestjs/common&#39;;

throw new HttpException(
  {
    statusCode: HttpStatus.BAD_REQUEST,
    message: &#39;입력한 값이 유효하지 않습니다.&#39;,
    error: &#39;Bad Request&#39;,
    details: {
      field: &#39;email&#39;,
      issue: &#39;이메일 형식이 아닙니다.&#39;,
    },
  },
  HttpStatus.BAD_REQUEST,
  {
    cause: new Error(&#39;ValidationError: 이메일 형식 오류&#39;),
  },
);</code></pre>
<h2 id="다음은-전체-응답-본문을-재정의하고-오류-원인을-제공하는-예입니다">다음은 전체 응답 본문을 재정의하고 오류 원인을 제공하는 예입니다.</h2>
<h3 id="생성자-인수-설명">생성자 인수 설명</h3>
<h4 id="1-response-첫-번째-인수">1. <strong><code>response</code> (첫 번째 인수)</strong></h4>
<ul>
<li>타입: <code>string</code> 또는 <code>object</code></li>
<li>역할: 클라이언트에 반환될 <strong>JSON 응답의 본문(body)</strong> 정의</li>
<li>위 예시에서는 아래 객체 전체가 클라이언트에게 응답됩니다:</li>
</ul>
<pre><code class="language-json">{
  &quot;statusCode&quot;: 400,
  &quot;message&quot;: &quot;입력한 값이 유효하지 않습니다.&quot;,
  &quot;error&quot;: &quot;Bad Request&quot;,
  &quot;details&quot;: {
    &quot;field&quot;: &quot;email&quot;,
    &quot;issue&quot;: &quot;이메일 형식이 아닙니다.&quot;
  }
}</code></pre>
<ul>
<li>이처럼 커스텀 필드(<code>details</code>)를 넣어서 응답을 세부적으로 제어할 수 있습니다.</li>
</ul>
<h4 id="2-status-두-번째-인수">2. <strong><code>status</code> (두 번째 인수)</strong></h4>
<ul>
<li>타입: <code>number</code> (HTTP 상태코드)</li>
<li>역할: 응답의 HTTP 상태 코드를 정의</li>
<li><code>HttpStatus.BAD_REQUEST</code>는 <code>400</code>이며, Nest에서는 <code>@nestjs/common</code>에서 제공하는 <code>HttpStatus</code> enum을 사용하는 것이 좋습니다.</li>
</ul>
<h4 id="3-options-세-번째-인수---선택">3. <strong><code>options</code> (세 번째 인수)</strong> - 선택</h4>
<ul>
<li>타입: <code>{ cause?: Error }</code></li>
<li>역할: 로깅 또는 디버깅용 <strong>내부 에러 정보</strong>를 담는 옵션</li>
<li>이 값은 클라이언트에게는 <strong>보이지 않지만</strong>, Nest의 내부 로거에서 예외 원인(<code>cause</code>)을 추적하거나 에러 스택을 기록하는 데 유용합니다.</li>
</ul>
<hr>
<h3 id="실제-사용-예-유효성-검사-실패-시">실제 사용 예: 유효성 검사 실패 시</h3>
<p>예를 들어, 사용자가 이메일 필드에 잘못된 값을 보냈을 때, 아래와 같이 사용할 수 있습니다:</p>
<pre><code class="language-ts">if (!isValidEmail(input.email)) {
  throw new HttpException(
    {
      statusCode: 400,
      message: &#39;입력한 이메일이 유효하지 않습니다.&#39;,
      error: &#39;Bad Request&#39;,
      details: {
        field: &#39;email&#39;,
        issue: &#39;형식이 이메일이 아닙니다.&#39;,
      },
    },
    HttpStatus.BAD_REQUEST,
    {
      cause: new Error(&#39;이메일 정규표현식 검사 실패&#39;),
    },
  );
}</code></pre>
<p>이렇게 하면 프론트엔드에서는 구체적인 오류 메시지를 기반으로 사용자에게 피드백을 줄 수 있고, 백엔드에서는 <code>cause</code>를 기반으로 로깅이나 디버깅이 용이해집니다.</p>
<h1 id="예외-로깅">예외 로깅</h1>
<p>기본적으로 예외 필터는 (해당 필터에서 상속되는 모든 예외)와 같은 내장 예외를 기록하지 않습니다 . 이러한 예외가 발생하면 일반 애플리케이션 흐름의 일부로 처리되므로 콘솔에 표시되지 않습니다.WsExceptionRpcException 및 HttpException와 같은 다른 내장 예외에도 동일한 동작이 적용됩니다 .</p>
<p>@nestjs/common IntrinsicException예외는 모두 패키지 에서 내보내는 기본 클래스를 상속합니다 . 이 클래스는 일반적인 애플리케이션 동작에 포함되는 예외와 그렇지 않은 예외를 구분하는 데 도움이 됩니다.</p>
<p>이러한 예외를 기록하려면 사용자 지정 예외 필터를 만들 수 있습니다. 다음 섹션에서 이 작업을 수행하는 방법을 설명하겠습니다.</p>
<h1 id="사용자-정의-예외">사용자 정의 예외</h1>
<p>대부분의 경우 사용자 지정 예외를 작성할 필요가 없으며,  기본 Nest HTTP 예외를 사용할 수 있습니다. 사용자 지정 예외를 생성해야 하는 경우, 사용자 지정 예외가 기본 클래스를 상속하는 자체 예외 계층 구조를 HttpException으로  만드는 것이 좋습니다 . 이 방법을 사용하면 Nest가 예외를 인식하고 오류 응답을 자동으로 처리합니다. 이러한 사용자 지정 예외를 구현해 보겠습니다.</p>
<pre><code class="language-ts">

export class ForbiddenException extends HttpException {
  constructor() {
    super(&#39;Forbidden&#39;, HttpStatus.FORBIDDEN);
  }
}
</code></pre>
<h1 id="내장된-http-예외">내장된 HTTP 예외</h1>
<p>Nest는 기본 .NET Framework에서 상속되는 표준 예외 집합을 제공합니다 HttpException. 이러한 예외는 @nestjs/common패키지에 포함되어 있습니다.</p>
<ul>
<li>BadRequestException</li>
<li>UnauthorizedException</li>
<li>NotFoundException</li>
<li>ForbiddenException</li>
<li>NotAcceptableException</li>
<li>RequestTimeoutException</li>
<li>ConflictException</li>
<li>GoneException</li>
<li>HttpVersionNotSupportedException</li>
<li>PayloadTooLargeException</li>
<li>UnsupportedMediaTypeException</li>
<li>UnprocessableEntityException</li>
<li>InternalServerErrorException</li>
<li>NotImplementedException</li>
<li>ImATeapotException</li>
<li>MethodNotAllowedException</li>
<li>BadGatewayException</li>
<li>ServiceUnavailableException</li>
<li>GatewayTimeoutException</li>
<li>PreconditionFailedException
모든 내장 예외는 매개변수 cause를 사용하여 오류와 오류 설명을 모두 제공할 수도 있습니다.</li>
</ul>
<h1 id="예외-필터">예외 필터</h1>
<p>기본(내장) 예외 필터는 여러 사례를 자동으로 처리할 수 있지만, 예외 계층을 완벽하게 제어 하고 싶을 수도 있습니다. 예를 들어, 로깅을 추가하거나 일부 동적 요소에 따라 다른 JSON 스키마를 사용하고 싶을 수 있습니다. 예외 필터는 바로 이러한 목적을 위해 설계되었습니다. 이를 통해 클라이언트로 전송되는 응답의 내용과 제어 흐름을 정확하게 제어할 수 있습니다.</p>
<p>클래스의 인스턴스인 예외를 포착하고, 이에 대한 사용자 지정 응답 로직을 구현하는 예외 필터를 만들어 보겠습니다 . 이를 위해서는 기본 플랫폼 과 HttpException 객체 에 접근해야 합니다 . 객체에 접근하여 원본 객체를 추출 하고 로깅 정보에 포함할 것입니다. 또한, 이 객체를 사용하여 메서드를 통해 전송되는 응답을 직접 제어합니다.</p>
<pre><code class="language-ts">import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  Logger,
} from &#39;@nestjs/common&#39;;
import { Request, Response } from &#39;express&#39;;

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(HttpExceptionFilter.name);

  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse&lt;Response&gt;();
    const request = ctx.getRequest&lt;Request&gt;();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    // 예외 로깅
    this.logger.error(
      `[${request.method}] ${request.url} ${status} - ${JSON.stringify(exceptionResponse)}`,
    );

    // 클라이언트에게 반환
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      ...(typeof exceptionResponse === &#39;string&#39;
        ? { message: exceptionResponse }
        : exceptionResponse),
    });
  }
}
</code></pre>
<h1 id="바인딩-필터">바인딩 필터</h1>
<pre><code class="language-ts">
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
  throw new ForbiddenException();
}
</code></pre>
<h1 id="또는-전역">또는 전역</h1>
<pre><code class="language-ts">
// main.ts
import { NestFactory } from &#39;@nestjs/core&#39;;
import { AppModule } from &#39;./app.module&#39;;
import { HttpExceptionFilter } from &#39;./filters/http-exception.filter&#39;;

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();</code></pre>
<h1 id="또는-전역-모듈">또는 전역 모듈</h1>
<pre><code class="language-ts">
import { Module } from &#39;@nestjs/common&#39;;
import { APP_FILTER } from &#39;@nestjs/core&#39;;

@Module({
  providers: [
    {
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },
  ],
})
export class AppModule {}

</code></pre>
<h1 id="catch-every">Catch Every</h1>
<p>모든 예외를 처리할 때에는 @Catch를 비워두세요</p>
<pre><code class="language-ts">
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
} from &#39;@nestjs/common&#39;;
import { HttpAdapterHost } from &#39;@nestjs/core&#39;;

@Catch()
export class CatchEverythingFilter implements ExceptionFilter {
  constructor(private readonly httpAdapterHost: HttpAdapterHost) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    // In certain situations `httpAdapter` might not be available in the
    // constructor method, thus we should resolve it here.
    const { httpAdapter } = this.httpAdapterHost;

    const ctx = host.switchToHttp();

    const httpStatus =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const responseBody = {
      statusCode: httpStatus,
      timestamp: new Date().toISOString(),
      path: httpAdapter.getRequestUrl(ctx.getRequest()),
    };

    httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
  }
}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 5.Middleware]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-5.Middleware</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-5.Middleware</guid>
            <pubDate>Thu, 05 Jun 2025 04:47:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/2f0abc5c-7a71-40b7-9563-4eae430af7df/image.png" alt=""></p>
<h1 id="미들웨어-middleware">미들웨어 (Middleware)</h1>
<hr>
<p>미들웨어는 라우트 핸들러 보다 먼저 호출되는 함수입니다 . 미들웨어 함수는 요청 및 응답 객체에 접근할 수 있으며, next()애플리케이션의 요청-응답 주기에서 미들웨어 함수에 접근할 수 있습니다. </p>
<p>Nest 미들웨어는 기본적으로 Express 미들웨어와 동일합니다. Express 공식 문서의 다음 설명은 미들웨어의 기능을 설명합니다.</p>
<p>미들웨어 기능은 다음과 같은 작업을 수행할 수 있습니다.</p>
<ul>
<li>모든 코드를 실행합니다.</li>
<li>요청 및 응답 객체를 변경합니다.</li>
<li>요청-응답 주기를 종료합니다.</li>
<li>스택의 다음 미들웨어 함수를 호출합니다.</li>
<li>미들웨어 함수가 요청-응답 주기를 종료하지 않으면 next()다음 미들웨어 함수로 제어권을 넘기기 위해 호출해야 합니다. 그렇지 않으면 요청은 중단 상태로 남게 됩니다.</li>
</ul>
<pre><code class="language-javascript">


import { Injectable, NestMiddleware } from &#39;@nestjs/common&#39;;
import { Request, Response, NextFunction } from &#39;express&#39;;

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(&#39;Request...&#39;);
    next();
  }
}
</code></pre>
<p>Spring 에서 Filter 코드</p>
<pre><code class="language-java">import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

@Component
public class LoggerInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        System.out.println(&quot;Request...&quot;);
        return true; // false 반환 시 요청 중단
    }
}
</code></pre>
<h1 id="종속성-주입">종속성 주입</h1>
<p>Nest 미들웨어는 의존성 주입을 완벽하게 지원합니다. 공급자와 컨트롤러와 마찬가지로, 동일한 모듈 내에서 사용 가능한 의존성을 주입 할 수 있습니다</p>
<h1 id="미들웨어-적용">미들웨어 적용</h1>
<p>데코레이터 에는 미들웨어를 위한 공간이 없습니다 . 대신 모듈 클래스의 메서드를 @Module()사용하여 미들웨어를 설정합니다 . 미들웨어를 포함하는 모듈은 인터페이스를 구현해야 합니다. 해당 레벨에서 미들웨어를 설정해 보겠습니다 </p>
<pre><code class="language-javascript">import { MiddlewareConsumer, Module, NestModule } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { CatsController } from &#39;./cats/cats.controller&#39;;
import { CatsModule } from &#39;./cats/cats.module&#39;;
import { LoggerMiddleware } from &#39;./logger/logger.middleware&#39;;


@Module({
  imports: [CatsModule],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule implements NestModule {  
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(&#39;cats&#39;);
  }
}
</code></pre>
<h2 id="특정-경로로-제한">특정 경로로 제한</h2>
<pre><code class="language-javascript">...
.forRoutes({ path: &#39;cats&#39;, method: RequestMethod.GET });</code></pre>
<h2 id="경로-와일드카드">경로 와일드카드</h2>
<p>패턴 기반 경로는 NestJS 미들웨어에서도 지원됩니다. 예를 들어, 명명된 와일드카드( )를 사용하여 경로의 모든 문자 조합을 일치시킬 수 있습니다. 다음 예제에서 미들웨어는 뒤에 오는 문자 수에 관계없이 *splat로 시작하는 모든 경로에 대해 실행됩니다 .</p>
<pre><code class="language-javascript">...
forRoutes({
  path: &#39;abcd/*splat&#39;,
  method: RequestMethod.ALL,
});</code></pre>
<p>경로 경로는 , , 등과 &#39;abcd/*&#39;일치합니다 . 하이픈( )과 마침표( )는 문자열 기반 경로에서 문자 그대로 해석됩니다. 그러나 추가 문자가 없으면 경로와 일치하지 않습니다. 이 경우 와일드카드를 중괄호로 묶어 선택 사항으로 만들어야 합니다.</p>
<pre><code class="language-javascript">
...

forRoutes({
  path: &#39;abcd/{*splat}&#39;,
  method: RequestMethod.ALL,
});
</code></pre>
<h1 id="경로-제외">경로 제외</h1>
<p>때로는 특정 경로를 미들웨어 적용에서 제외exclude() 해야 할 수 있습니다. 이는 메서드를 사용하여 쉽게 구현할 수 있습니다. 이 메서드는 제외할 경로를 식별하기 위해 exclude()단일 문자열, 여러 문자열 또는 객체를 받습니다.</p>
<pre><code class="language-javascript">

consumer
  .apply(LoggerMiddleware)
  .exclude(
    { path: &#39;cats&#39;, method: RequestMethod.GET },
    { path: &#39;cats&#39;, method: RequestMethod.POST },
    &#39;cats/{*splat}&#39;,
  )
  .forRoutes(CatsController);
</code></pre>
<p>위의 예제를 사용하면 메서드 에 전달된 세 개의 경로를 제외한LoggerMiddleware , 내부에 정의된 모든 경로에 바인딩됩니다 </p>
<p>이 접근 방식은 특정 경로나 경로 패턴에 따라 미들웨어를 적용하거나 제외하는 데 있어 유연성을 제공합니다.</p>
<h1 id="함수형-미들웨어">함수형 미들웨어</h1>
<p>우리가 사용해 온 클래스 LoggerMiddleware는 매우 간단합니다. 멤버도, 추가 메서드도, 종속성도 없습니다. 왜 클래스 대신 간단한 함수로 정의할 수 없는 걸까요? 사실, 가능합니다. 이러한 유형의 미들웨어를 함수형 미들웨어 라고 합니다 . 차이점을 보여주기 위해 로거 미들웨어를 클래스 기반에서 함수형 미들웨어로 변환해 보겠습니다.</p>
<pre><code class="language-typescript">
import { Request, Response, NextFunction } from &#39;express&#39;;

export function logger(req: Request, res: Response, next: NextFunction) {
  console.log(`Request...`);
  next();
};

</code></pre>
<h1 id="다중-미들웨어">다중 미들웨어</h1>
<p>위에서 언급했듯이 순차적으로 실행되는 여러 미들웨어를 바인딩하려면 apply()메서드 내부에 쉼표로 구분된 목록을 제공하기만 하면 됩니다.</p>
<pre><code class="language-typescript">import { MiddlewareConsumer, Module, NestModule, RequestMethod } from &#39;@nestjs/common&#39;;
import { AppController } from &#39;./app.controller&#39;;
import { AppService } from &#39;./app.service&#39;;
import { CatsController } from &#39;./cats/cats.controller&#39;;
import { CatsModule } from &#39;./cats/cats.module&#39;;
import { logger, LoggerMiddleware } from &#39;./logger/logger.middleware&#39;;


@Module({
  imports: [CatsModule],
  controllers: [AppController, CatsController],
  providers: [AppService],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {

    consumer
      .apply(LoggerMiddleware,logger)
      .exclude(
        { path: &#39;cats&#39;, method: RequestMethod.GET },
        { path: &#39;cats&#39;, method: RequestMethod.POST },
        &#39;cats/{*splat}&#39;,
      )
      .forRoutes(CatsController);


  }
}

</code></pre>
<h2 id="글로벌-미들웨어">글로벌 미들웨어</h2>
<p>등록된 모든 경로에 미들웨어를 한 번에 바인딩하려면 인스턴스 use()에서 제공하는 메서드를 사용할 수 있습니다.</p>
<pre><code class="language-typescript">const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(process.env.PORT ?? 3000);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 4.Modules]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-4.Modules</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-4.Modules</guid>
            <pubDate>Mon, 02 Jun 2025 06:12:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/3b802b1d-430c-4129-adf4-321faa051a75/image.png" alt="">
모듈은 데코레이터로 주석이 달린 클래스입니다.
이 데코레이터는 Nest가 애플리케이션 구조를 효율적으로 구성하고 관리하는 데 사용하는 메타데이터를 제공합니다.</p>
<p>모든 Nest 애플리케이션에는 최소 하나의 모듈, 즉 루트 모듈 이 있으며, 이 모듈은 Nest가 애플리케이션 그래프를 구축하는 시작점 역할을 합니다 . 이 그래프는 Nest가 모듈과 제공자 간의 관계와 종속성을 확인하는 데 사용하는 내부 구조입니다. 소규모 애플리케이션에는 루트 모듈만 있을 수 있지만, 일반적으로는 그렇지 않습니다. 모듈은 구성 요소를 효과적으로 구성하는 방법으로 적극 권장됩니다 . 대부분의 애플리케이션에는 여러 모듈이 있으며, 각 모듈은 밀접하게 관련된 기능 집합을 캡슐화합니다 .</p>
<p>데코레이터 @Module()는 모듈을 설명하는 속성을 가진 단일 객체를 가져옵니다.</p>
<h1 id=""></h1>
<ul>
<li><p>providers:    Nest 인젝터에 의해 인스턴스화되고 최소한 이 모듈 전체에서 공유될 수 있는 공급자</p>
</li>
<li><p>controllers:    이 모듈에 정의된 컨트롤러 세트는 인스턴스화되어야 합니다.</p>
</li>
<li><p>imports:    이 모듈에 필요한 공급자를 내보내는 가져온 모듈 목록</p>
</li>
<li><p>exports:    이 모듈에서 제공하는 하위 집합은 providers이 모듈을 가져오는 다른 모듈에서도 사용할 수 있어야 합니다. 제공자 자체를 사용하거나 토큰( provide값) 만 사용할 수 있습니다.</p>
<p>모듈은 기본적으로 프로바이더를 캡슐화합니다 . 즉, 현재 모듈에 포함되어 있거나 다른 가져온 모듈에서 명시적으로 내보낸 프로바이더만 주입할 수 있습니다. 모듈에서 내보낸 프로바이더는 기본적으로 해당 모듈의 공용 인터페이스 또는 API 역할을 합니다.</p>
</li>
</ul>
<h1 id="기능-모듈">기능 모듈</h1>
<p>CatsController는 CatsService밀접한 관련이 있으며 동일한 애플리케이션 도메인을 담당합니다. 따라서 이들을 기능 모듈로 그룹화하는 것이 좋습니다. 기능 모듈은 특정 기능과 관련된 코드를 구성하여 명확한 경계를 유지하고 더 나은 구성을 유지하는 데 도움이 됩니다. 이는 애플리케이션이나 팀이 성장함에 따라 특히 중요하며, SOLID 원칙과도 일치합니다.</p>
<p>CatsModule다음으로, 컨트롤러와 서비스를 그룹화하는 방법을 보여주기 위해 를 생성하겠습니다 .</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 3.Provider]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-3.Provider</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-3.Provider</guid>
            <pubDate>Thu, 29 May 2025 08:50:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/ae26c1c3-e7c0-47bd-9e4d-1b3284697b39/image.png" alt="">
공급자는 Nest의 핵심 개념입니다. 서비스, 저장소, 팩토리, 헬퍼와 같은 많은 기본 Nest 클래스는 공급자로 취급될 수 있습니다. 공급자의 핵심 아이디어는 종속성으로 주입 될 수 있어 객체가 서로 다양한 관계를 형성할 수 있다는 것입니다. 이러한 객체를 &quot;연결&quot;하는 책임은 주로 Nest 런타임 시스템에서 처리</p>
<h1 id="cli">CLI</h1>
<hr>
<pre><code class="language-shell">nest g service cats
</code></pre>
<h1 id="code">Code</h1>
<hr>
<h2 id="service">Service</h2>
<pre><code class="language-javascript">
import { Injectable } from &#39;@nestjs/common&#39;;
import { Cat } from &#39;./interfaces/cat.interface&#39;;

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
    this.cats.push(cat);
  }

  findAll(): Cat[] {
    return this.cats;
  }
}
</code></pre>
<p>이제 고양이를 검색하는 서비스 클래스가 생겼으니, 이를 : 내부에서 사용해 보겠습니다</p>
<h2 id="controller">Controller</h2>
<pre><code class="language-javascript">
import { Controller, Get, Post, Body } from &#39;@nestjs/common&#39;;
import { CreateCatDto } from &#39;./dto/create-cat.dto&#39;;
import { CatsService } from &#39;./cats.service&#39;;
import { Cat } from &#39;./interfaces/cat.interface&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise&lt;Cat[]&gt; {
    return this.catsService.findAll();
  }
}

</code></pre>
<p>클래스 생성자를 통해 주입됩니다</p>
<h1 id="종속성-주입">종속성 주입</h1>
<hr>
<p>Nest는 의존성 주입(Dependency Injection) 이라는 강력한 디자인 패턴을 기반으로 구축되었습니다 . 공식 Angular 문서 에서 이 개념에 대한 유용한 글을 읽어보시기를 강력히 추천합니다 .</p>
<p>Nest에서는 TypeScript의 기능 덕분에 종속성이 유형에 따라 해결되므로 관리가 간편합니다. 아래 예시에서 Nest는 catsService인스턴스를 생성하고 반환하여 종속성을 해결합니다 CatsService(싱글턴의 경우 이미 다른 곳에서 요청된 경우 기존 인스턴스를 반환합니다). 이 종속성은 컨트롤러의 생성자에 주입되거나 지정된 속성에 할당됩니다.</p>
<h1 id="범위">범위</h1>
<hr>
<p>공급자는 일반적으로 애플리케이션 수명 주기와 일치하는 수명(&quot;범위&quot;)을 갖습니다. 애플리케이션이 부트스트랩될 때 각 종속성을 해결해야 하므로 모든 공급자가 인스턴스화됩니다. 마찬가지로 애플리케이션이 종료되면 모든 공급자가 삭제됩니다. 하지만 공급자를 요청 범위로 지정할 수도 있습니다. 즉, 공급자의 수명이 애플리케이션의 수명 주기가 아닌 특정 요청에 따라 결정됩니다. 이러한 기법에 대한 자세한 내용은 주입 범위 장 에서 확인할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 2. Controllers]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-2.-Controllers</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-2.-Controllers</guid>
            <pubDate>Wed, 28 May 2025 14:12:16 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/7533890f-5684-4801-90e6-f47a80af349f/image.png" alt="">
<a href="https://docs.nestjs.com/controllers">출처</a></p>
<h1 id="controllers">Controllers</h1>
<hr>
<p>NestJS에서는 Spring과 마찬가지로 controller에서 들어오는 요청을 처리하고 클라이언트에게 응답을 다시 보내는 역할을 합니다 .</p>
<p>Controller에서는 애플리케이션의 특정 요청을 처리합니다.
NestJS에서의 Controller는 Spring과 마찬가지로 @Controller <strong>데코레이터</strong>를 사용합니다.</p>
<blockquote>
<p>메타프로그래밍 기능을 사용해 클래스, 메서드, 파라미터 등에 추가 정보를 붙이는 문법. Nest의 선언형 프로그래밍 핵심
메타프로그래밍(Metaprogramming)이란 자기 자신 혹은 다른 컴퓨터 프로그램을 데이터로 취급하며 프로그램을 작성·수정하는 것을 말한다. 넓은 의미에서, 런타임에 수행해야 할 작업의 일부를 컴파일 타임 동안 수행하는 프로그램을 말하기도 한다.</p>
</blockquote>
<p>@Controller 또는 클래스 메소드에 작성된 @Get,@Post 등의 데코레이터에 입력된 문자열은 애플리케이션에서 들어오는 요청에 대한 경로를 의미합니다.</p>
<p><strong>라우팅 메커니즘</strong>은 각 요청을 처리할 컨트롤러를 결정합니다. </p>
<blockquote>
<p>라우팅 메커니즘 클라이언트의 요청 URL과 HTTP 메서드(GET, POST 등)를 해당 컨트롤러 메서드에 연결하는 기능</p>
</blockquote>
<p>컨트롤러는 여러 경로를 가지며, 각 경로는 서로 다른 작업을 수행할 수 있습니다.</p>
<p>기본 컨트롤러를 만들려면 클래스와 데코레이터를 사용합니다 . 데코레이터는 클래스를 필요한 메타데이터와 연결하여 Nest가 요청을 해당 컨트롤러에 연결하는 라우팅 맵을 생성할 수 있도록 합니다.</p>
<h1 id="라우팅">라우팅</h1>
<p>다음 예제에서는 기본 컨트롤러를 정의하는 데 필요한@Controller() 데코레이터를 사용합니다. 선택적으로 경로 경로 접두사로 . 를 지정합니다 . 데코레이터에 경로 접두사를 사용하면 관련 경로를 그룹화하고 반복적인 코드를 줄일 수 있습니다. 예를 들어, 경로 아래에 있는 cat 엔터티와의 상호 작용을 관리하는 경로를 그룹화하려면 데코레이터 에 경로 접두사를 지정할 수 있습니다 . 이렇게 하면 파일의 각 경로에 대해 해당 경로 부분을 반복할 필요가 없습니다.cats@Controller()/catscats@Controller()</p>
<pre><code class="language-javascript">import { Controller, Get } from &#39;@nestjs/common&#39;;

@Controller(&#39;cats&#39;)
export class CatsController {
  @Get()
  findAll(): string {
    return &#39;This action returns all cats&#39;;
  }
}</code></pre>
<h1 id="cli">CLI</h1>
<hr>
<pre><code class="language-shell">nest g controller [name] </code></pre>
<p>명령어를 통해서 controller 를 class 파일을 생성할 수 있습니다.</p>
<h1 id="기타">기타</h1>
<hr>
<ul>
<li>NestJS Controller는  JavaScript 객체 또는 배열을 반환할 때 JSON으로 자동 직렬화</li>
<li>응답의 상태 코드는 기본적으로 항상 200입니다. 단, POST 요청은 201을 사용합니다. @HttpCode(...)핸들러 수준에서 데코레이터를 추가하여 이 동작을 쉽게 변경할 수 있습니다</li>
<li>응답 객체를 사용할 수 있으며 , 이 객체는 @Res()메서드 핸들러 시그니처(예: findAll(@Res() response))에 데코레이터를 사용하여 주입할 수 있습니다. 이 방식을 사용하면 해당 객체가 제공하는 네이티브 응답 처리 메서드를 사용할 수 있습니다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Nestjs Study] 1. Nestjs는?]]></title>
            <link>https://velog.io/@hiio420_official/Nestjs-Study-1.-Nestjs%EB%8A%94</link>
            <guid>https://velog.io/@hiio420_official/Nestjs-Study-1.-Nestjs%EB%8A%94</guid>
            <pubDate>Tue, 27 May 2025 07:42:07 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hiio420_official/post/7983a18b-3182-4b45-a17d-3ad1f92a4e06/image.png" alt=""></p>
<h1 id="hello-nest">Hello, nest</h1>
<hr>
<p>NestJS에 대해서 처음 접했던 건 아르바이트로 참여했던 프로젝트에서 사용했던 스택으로 시작합니다. 기존에는 Spring 또는 전자정부프레임워크로 진행하는 SI 프로젝트에 참여하고 있었습니다.
Javascript로 기존의 화면 기능에대한 부분을 개발하고 있었기 때문에 언어에 대한 부담감은 없었지만, 새로운 프레임워크를 사용한다는 건 언제나 두근두근 하게 만드는 것 같습니다.</p>
<p>막연하게나마 어떻게 개발을 하게 될까라는 호기심으로 배움으로 또는 새로운 영역에 대한 확장으로 이어질 수 있지 않을까라는 기대감을 가지게 됩니다.</p>
<p>처음 프레임워크나 라이브러리를 사용할때 다들 어떻게 하시나요 ?
요즘은 아무래도 Chatgpt를 사용하거나, Cursor나 windsurf등으로 개발을 하게되면 쉽게 특정 부분에 대한 내용을 바로 코딩해주기 때문에 편할 걸로 생각됩니다.</p>
<p>스터디 겸 정리 겸 nestjs 공식문서를 참고해 볼 까 합니다.</p>
<h1 id="nestjs"><a href="https://docs.nestjs.com/">NestJS</a></h1>
<h1 id="introduction">Introduction</h1>
<hr>
<blockquote>
<p>Nest (NestJS) is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript) and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Reactive Programming).
Under the hood, Nest makes use of robust HTTP Server frameworks like Express (the default) and optionally can be configured to use Fastify as well!
Nest provides a level of abstraction above these common Node.js frameworks (Express/Fastify), but also exposes their APIs directly to the developer. This gives developers the freedom to use the myriad of third-party modules which are available for the underlying platform.<a href="https://docs.nestjs.com/">출처</a></p>
</blockquote>
<p>공식문서에서 소개하는 NestJS에 대한 내용입니다.</p>
<p>요약하면</p>
<ul>
<li><p>NestJS는 효율적이고 확장 가능한 Node.js 기반 서버 프레임워크</p>
</li>
<li><p>pure Javascript 와 Typescript 작성 및 지원</p>
</li>
<li><p>객체지향 프로그래밍(OOP)</p>
<blockquote>
<p><strong>객체지향 프로그래밍(Object-Oriented Programming, OOP)</strong>은 데이터를 객체로 모델링하여 소프트웨어를 설계하는 프로그래밍 패러다임입니다. 각 객체는 속성(데이터)과 메서드(동작)를 포함하며, 주요 특징으로는 캡슐화, 상속, 다형성, 추상화가 있습니다.</p>
</blockquote>
</li>
<li><p>함수형 프로그래밍 (FP)</p>
<blockquote>
<p><strong>함수형 프로그래밍(Functional Programming, FP)</strong>은 순수 함수를 기반으로 하는 선언형 프로그래밍 패러다임입니다. 상태 변경이나 부작용을 피하고, 함수를 일급 시민으로 취급하여 고차 함수, 불변성, 함수 합성 등을 강조합니다. </p>
</blockquote>
</li>
<li><p>함수형 반응형 프로그래밍(FRP) </p>
<blockquote>
<p><strong>함수형 반응형 프로그래밍(Functional Reactive Programming, FRP)</strong>은 함수형 프로그래밍과 반응형 프로그래밍을 결합한 패러다임으로, 시간에 따라 변하는 데이터 흐름을 선언적으로 모델링합니다. 이벤트 스트림과 동작(behavior)을 조합하여 비동기 데이터 흐름을 효율적으로 처리합니다.</p>
</blockquote>
</li>
<li><p>기본적으로 Express를 사용 </p>
<blockquote>
<p>Express는 Node.js를 위한 빠르고 유연한 웹 애플리케이션 프레임워크로, 최소한의 기능으로 웹 및 모바일 애플리케이션을 구축할 수 있도록 지원합니다. 라우팅, 미들웨어, HTTP 유틸리티 메서드 등을 제공하여 RESTful API 개발에 널리 사용됩니다.</p>
</blockquote>
</li>
<li><p>선택적으로 Fastify로 구성할 수 있음</p>
<blockquote>
<p>Fastify는 Node.js를 위한 고성능 웹 프레임워크로, 낮은 오버헤드와 강력한 플러그인 아키텍처를 특징으로 합니다. Express보다 빠른 요청 처리를 목표로 하며, JSON 직렬화 및 비동기 처리를 최적화하여 대규모 애플리케이션에 적합합니다. </p>
</blockquote>
</li>
<li><p>Node.js 프레임워크(Express/Fastify) 위에 추상화 계층 제공, 동시에 개발자에게 API를 직접 노출</p>
</li>
<li><p>수많은 서드 파티 모듈들을 자유롭게 사용가능</p>
</li>
</ul>
<p>위와 같은 내용으로 설명하고 있습니다.</p>
<p>nestjs를 시작하려면 어떻게 해야할까요?</p>
<h1 id="nest-cli"><a href="https://docs.nestjs.com/cli/overview">Nest CLI</a></h1>
<hr>
<p>먼저 nest CLI 를 사용하기 위한 패키지 설치를 해야합니다.공식 문서에서는 npm을 이용해 패키지를 관리합니다.</p>
<pre><code class="language-bash">npm install -g @nestjs/cli</code></pre>
<p>@nestjs/cli 패키지를 글로벌로 설치하면 nest 명령어를 사용할 수 있습니다.</p>
<pre><code class="language-bash">nest --help</code></pre>
<p>보통 cli 커맨드에서 제공하는 --help를 nest cli에서 사용하면 nest cli 에서 사용가능한 명령어 목록을 볼 수 있습니다.</p>
<pre><code>
Options:
  -v, --version                                   Output the current version.
  -h, --help                                      Output usage information.

Commands:
  new|n [options] [name]                          Generate Nest application.
  build [options] [apps...]                       Build Nest application.
  start [options] [app]                           Run Nest application.
  info|i                                          Display Nest project details.
  add [options] &lt;library&gt;                         Adds support for an external library to your project.
  generate|g [options] &lt;schematic&gt; [name] [path]  Generate a Nest element.
    Schematics available on @nestjs/schematics collection:
      ┌────────────────┬──────────────┬───────────────────────────────────────────────────┐
      │ name          │ alias       │ description                                  │
      │ application   │ application │ Generate a new application workspace         │
      │ class         │ cl          │ Generate a new class                         │
      │ configuration │ config      │ Generate a CLI configuration file            │
      │ controller    │ co          │ Generate a controller declaration            │
      │ decorator     │ d           │ Generate a custom decorator                  │
      │ filter        │ f           │ Generate a filter declaration                │
      │ gateway       │ ga          │ Generate a gateway declaration               │
      │ guard         │ gu          │ Generate a guard declaration                 │
      │ interceptor   │ itc         │ Generate an interceptor declaration          │
      │ interface     │ itf         │ Generate an interface                        │
      │ library       │ lib         │ Generate a new library within a monorepo     │
      │ middleware    │ mi          │ Generate a middleware declaration            │
      │ module        │ mo          │ Generate a module declaration                │
      │ pipe          │ pi          │ Generate a pipe declaration                  │
      │ provider      │ pr          │ Generate a provider declaration              │
      │ resolver      │ r           │ Generate a GraphQL resolver declaration      │
      │ resource      │ res         │ Generate a new CRUD resource                 │
      │ service       │ s           │ Generate a service declaration               │
      │ sub-app       │ app         │ Generate a new application within a monorepo │
      └────────────────┴───────────────┴──────────────────────────────────────────────────┘</code></pre><h1 id="프로젝트-생성">프로젝트 생성</h1>
<hr>
<p>프로젝트 생성은 new 명령어를 통해 생성합니다.</p>
<pre><code class="language-bash">nest new &lt;project_name&gt;</code></pre>
<p>공식문서에서는 starter 프로젝트를 제공합니다.</p>
<pre><code class="language-bash">git clone https://github.com/nestjs/typescript-starter.git project
cd project
npm install
npm run start</code></pre>
<p>프로젝트를 실행하면 <a href="http://localhost:3000">http://localhost:3000</a> 주소로 접속
어떤 프로그래밍이든 처음 시작하면 만날 수 있는 Hello World 문구가 보입니다.</p>
<h1 id="마무리">마무리</h1>
<hr>
<p>여기까지 NestJS에 대한 설명을 공식문서를 통해서 알아보고 starter 프로젝트를 실해시켜 보았습니다.</p>
<p>처음이지만 다른 프레임워크들과 마찬가지로 설치나 프로젝트 생성은 nodejs, npm을 통해서 쉽게 사용할 수 있어 좋은 것 같습니다.</p>
<p>다음은 starter 프로젝트와 공식문서를 함께 살펴보면서 프로젝트 구조는 어떻게 되어 있는지 알아보면 좋을 거 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MFA(Multi-factor authentication)]]></title>
            <link>https://velog.io/@hiio420_official/MFAMulti-factor-authentication</link>
            <guid>https://velog.io/@hiio420_official/MFAMulti-factor-authentication</guid>
            <pubDate>Tue, 27 May 2025 03:15:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>다요소 인증(多要素認證, Multi-factor authentication, MFA)은 적어도 다음 분류 중 두 가지에 한해 별도의 여러 증거 부분을 인증 매커니즘에 성공적으로 제시한 이후에만 사용자가 접근 권한이 주어지는 컴퓨터 접근 제어 방식의 하나이다: 지식(knowledge), 소유(possession), 속성(inherence).<a href="https://ko.wikipedia.org/wiki/%EB%8B%A4%EC%9A%94%EC%86%8C_%EC%9D%B8%EC%A6%9D">출처:위키</a></p>
</blockquote>
<p>다중 인증(MFA, Multi-Factor Authentication)은 사용자의 신원을 확인하기 위해 두 가지 이상의 인증 요소를 요구하는 보안 절차입니다. 이는 단일 인증 방식보다 보안을 강화하여 무단 접근을 방지하는 데 효과적입니다.</p>
<h1 id="역사와-배경">역사와 배경</h1>
<hr>
<h3 id="1-초기-형태-atm과-pin">1. 초기 형태: ATM과 PIN</h3>
<p>1967년 영국 Barclays 은행에서 도입된 최초의 ATM은 카드(소유 요소)와 PIN(지식 요소)을 요구하여 초기의 2단계 인증 형태를 구현했습니다. </p>
<h3 id="2-1990년대-기업용-2fa-도입">2. 1990년대: 기업용 2FA 도입</h3>
<p>1990년대 후반, AT&amp;T는 하드웨어 토큰을 활용한 2FA 시스템을 개발했습니다. 이러한 시스템은 기업 환경에서 보안을 강화하는 데 사용되었습니다. </p>
<h3 id="3-2000년대-소비자용-2fa-확산">3. 2000년대: 소비자용 2FA 확산</h3>
<p>2000년대 초반, 온라인 뱅킹 및 전자상거래의 성장과 함께 소비자용 2FA가 확산되었습니다. SMS 기반 OTP와 같은 방식이 일반화되었으며, 이는 피싱 공격과 같은 위협에 대응하기 위한 조치였습니다.</p>
<h3 id="4-2010년대-이후-모바일-및-생체-인증">4. 2010년대 이후: 모바일 및 생체 인증</h3>
<p>스마트폰의 보급과 함께 모바일 앱 기반의 인증 방식이 등장했으며, 지문, 얼굴 인식 등 생체 인증 기술도 MFA에 통합되었습니다. 또한, FIDO2 및 WebAuthn과 같은 표준이 개발되어 비밀번호 없는 인증 환경이 조성되고 있습니다. </p>
<h1 id="구성요소">구성요소</h1>
<hr>
<p>MFA는 일반적으로 다음 세 가지 요소 중 두 가지 이상을 조합하여 사용합니다:</p>
<h2 id="지식-요소-사용자가-알고-있는-정보-예-비밀번호-pin">지식 요소: 사용자가 알고 있는 정보 (예: 비밀번호, PIN)</h2>
<h2 id="소유-요소-사용자가-소유한-물리적-장치-예-스마트폰-보안-토큰">소유 요소: 사용자가 소유한 물리적 장치 (예: 스마트폰, 보안 토큰)</h2>
<h2 id="고유-요소-사용자의-생체-정보-예-지문-얼굴-인식">고유 요소: 사용자의 생체 정보 (예: 지문, 얼굴 인식)</h2>
<hr>
<h1 id="고려사항">고려사항</h1>
<hr>
<h2 id="비밀번호">비밀번호:</h2>
<p>강력한 비밀번호 정책 필요 (예: 길이, 복잡성, 반복 방지 등)</p>
<p>해시(예: bcrypt, Argon2) 처리 필수</p>
<h2 id="토큰-앱-sms-이메일">토큰, 앱, SMS, 이메일:</h2>
<p>SMS/이메일 OTP: 일정 시간 만료, 횟수 제한, 서버-클라이언트 동기화 필요</p>
<p>Authenticator App (TOTP): 시간 기반 알고리즘(HOTP/TOTP), Google Authenticator, Authy 등 연동</p>
<p>FIDO2/WebAuthn: 생체 기반 또는 보안키 기반 → 브라우저/디바이스 호환성 고려</p>
<h2 id="생체-정보-기반">생체 정보 기반:</h2>
<p>일반적으로 클라이언트(기기)에 저장, 서버에는 생체 정보 저장 안함</p>
<p>WebAuthn API, Android/iOS 생체 인증 API 사용</p>
<h1 id="보안-고려사항">보안 고려사항</h1>
<hr>
<p>MFA 인증 흐름을 탈취 방지 설계</p>
<p>토큰 재사용 불가, 단일 사용성 확보</p>
<p>TLS(HTTPS) 적용 필수</p>
<p>타이밍 공격, 브루트포스 방지</p>
<p>MFA 실패 시 반복 제한 및 알림 기능</p>
<p>TOTP 사용 시 서버와 클라이언트의 시간 동기화 관리</p>
<h1 id="사용자-경험ux-최적화">사용자 경험(UX) 최적화</h1>
<hr>
<p>MFA 등록/삭제 기능 제공 (사용자 선택 가능하게)</p>
<p>백업 수단 제공 (예: 백업 코드, 보조 이메일)</p>
<p>MFA 단계에서의 안내 메시지 명확화 (예: &quot;인증 코드를 입력하세요&quot;, &quot;앱에서 승인해주세요&quot;)</p>
<p>MFA 오류 시 유연한 예외 처리 (예: 디바이스 분실 시)</p>
<h1 id="기술-스택별-구현-도구">기술 스택별 구현 도구</h1>
<table>
<thead>
<tr>
<th>요소</th>
<th>라이브러리/기술</th>
</tr>
</thead>
<tbody><tr>
<td>OTP 생성/검증</td>
<td><code>speakeasy</code>, <code>otplib</code>, <code>google-authenticator</code></td>
</tr>
<tr>
<td>SMS 발송</td>
<td>Twilio, MessageBird, AWS SNS</td>
</tr>
<tr>
<td>이메일 인증</td>
<td>nodemailer, Amazon SES</td>
</tr>
<tr>
<td>WebAuthn</td>
<td><code>@simplewebauthn/server</code>, <code>webauthn-json</code></td>
</tr>
<tr>
<td>상태 저장</td>
<td>Redis (TTL 기반) or DB (PostgreSQL, MongoDB)</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로토콜 Protocol]]></title>
            <link>https://velog.io/@hiio420_official/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-Protocol</link>
            <guid>https://velog.io/@hiio420_official/%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-Protocol</guid>
            <pubDate>Fri, 23 May 2025 04:11:18 GMT</pubDate>
            <description><![CDATA[<h1 id="프로토콜protocol">프로토콜(Protocol)</h1>
<blockquote>
<p>통신 프로토콜 또는 통신 규약은 컴퓨터나 원거리 통신 장비 사이에서 메시지를 주고 받는 양식과 규칙의 체계이다. 통신 프로토콜은 신호 체계, 인증, 그리고 오류 감지 및 수정 기능을 포함할 수 있다. 프로토콜은 형식, 의미론, 그리고 통신의 동기 과정 등을 정의하기는 하지만 구현되는 방법은 독립적이다. 이때문에 프로토콜은 하드웨어 또는 소프트웨어 그리고 때로는 모두를 사용하여 구현되기도 한다. <a href="https://ko.wikipedia.org/wiki/%ED%86%B5%EC%8B%A0_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C">출처:위키</a></p>
</blockquote>
<p>프로토콜이란 단어를 생각보다 많이 듣게 되지도, 사용하지도 않아습니다.
보통 HTTP,HTTPS,MCP,TCP/IP 등 용어안에서 다른 단어들과 함께 사용되기 때문이지 않을까 생각됩니다.</p>
<h2 id="프로토콜의-주요-종류">프로토콜의 주요 종류</h2>
<h3 id="1-네트워크-계층-프로토콜">1. 네트워크 계층 프로토콜</h3>
<ul>
<li>IP (Internet Protocol): 데이터 패킷의 주소 지정과 라우팅을 담당합니다.</li>
<li>TCP (Transmission Control Protocol): 신뢰성 있는 데이터 전송을 보장하며, 연결 지향적입니다.</li>
<li>UDP (User Datagram Protocol): 비연결형 프로토콜로, 빠른 전송이 필요할 때 사용됩니다.</li>
</ul>
<h3 id="2-응용-계층-프로토콜">2. 응용 계층 프로토콜</h3>
<ul>
<li>HTTP/HTTPS: 웹 브라우저와 서버 간의 통신을 위한 프로토콜입니다.</li>
<li>FTP: 파일 전송을 위한 프로토콜입니다.</li>
<li>SMTP/IMAP/POP3: 이메일 송수신을 위한 프로토콜입니다.</li>
</ul>
<h3 id="3-데이터-직렬화-및-api-통신-프로토콜">3. 데이터 직렬화 및 API 통신 프로토콜</h3>
<ul>
<li>JSON: 가볍고 인간이 읽기 쉬운 데이터 형식으로, 웹 API에서 널리 사용됩니다.</li>
<li>XML: 구조화된 데이터를 표현하는 데 사용되며, 과거에 많이 사용되었습니다.</li>
<li>GraphQL: 클라이언트가 필요한 데이터만 요청할 수 있게 해주는 쿼리 언어입니다.</li>
<li>Protocol Buffers (Protobuf): Google에서 개발한 이진 직렬화 형식으로, 빠르고 효율적인 데이터 전송을 제공합니다.</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>백엔드 개발자 관점</th>
<th>프론트엔드 개발자 관점</th>
</tr>
</thead>
<tbody><tr>
<td><strong>주요 관심 프로토콜</strong></td>
<td>HTTP/HTTPS, TCP/IP, WebSocket, gRPC</td>
<td>HTTP/HTTPS, WebSocket, GraphQL</td>
</tr>
<tr>
<td><strong>데이터 직렬화 포맷</strong></td>
<td>JSON, XML, Protobuf</td>
<td>JSON, GraphQL</td>
</tr>
<tr>
<td><strong>통신 방식</strong></td>
<td>RESTful API, gRPC, WebSocket</td>
<td>RESTful API, GraphQL, WebSocket</td>
</tr>
<tr>
<td><strong>보안 고려사항</strong></td>
<td>인증/인가, 데이터 암호화, CORS 설정</td>
<td>HTTPS 사용, CORS 정책 이해</td>
</tr>
<tr>
<td><strong>성능 최적화</strong></td>
<td>데이터베이스 쿼리 최적화, 캐싱 전략</td>
<td>Lazy Loading, 코드 스플리팅</td>
</tr>
<tr>
<td><strong>에러 처리</strong></td>
<td>서버 로그 분석, 예외 처리 로직 구현</td>
<td>사용자 피드백 제공, 에러 메시지 표시</td>
</tr>
<tr>
<td><strong>테스트 도구</strong></td>
<td>Postman, Swagger, JMeter</td>
<td>Postman, 브라우저 개발자 도구</td>
</tr>
</tbody></table>
<p>프로토콜은 백엔드와 프론트엔드 개발자 모두에게 필수적인 개념입니다. 백엔드는 데이터의 처리와 보안을, 프론트엔드는 사용자와의 원활한 상호작용을 위해 프로토콜을 활용합니다. 각자의 역할에 맞는 프로토콜의 이해와 활용이 성공적인 애플리케이션 개발의 핵심입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Http / Https]]></title>
            <link>https://velog.io/@hiio420_official/Http-Https</link>
            <guid>https://velog.io/@hiio420_official/Http-Https</guid>
            <pubDate>Thu, 22 May 2025 14:36:50 GMT</pubDate>
            <description><![CDATA[<h1 id="http">HTTP</h1>
<hr>
<h2 id="http란">HTTP란?</h2>
<p>HTTP(HyperText Transfer Protocol)는 웹 브라우저(클라이언트)와 웹 서버 간에 정보를 주고받기 위한 통신 규약입니다. 1989년 팀 버너스 리(Tim Berners-Lee)가 CERN에서 하이퍼텍스트 기반의 정보 공유를 위해 개발하였으며, 이후 월드 와이드 웹(WWW)의 핵심 프로토콜로 자리 잡았습니다.</p>
<h3 id="http의-주요-특징">HTTP의 주요 특징</h3>
<p>비연결성(Connectionless): 각 요청마다 독립적으로 처리되며, 연결이 유지되지 않습니다.</p>
<p>무상태성(Stateless): 서버는 이전 요청의 상태를 저장하지 않습니다.</p>
<p>텍스트 기반 프로토콜: 사람이 읽을 수 있는 형태로 메시지를 주고받습니다.</p>
<p>클라이언트-서버 모델: 클라이언트가 요청을 보내고, 서버가 응답을 제공합니다.</p>
<h3 id="http-통신-과정">HTTP 통신 과정</h3>
<p>클라이언트 요청: 브라우저가 서버에 요청을 보냅니다.</p>
<p>서버 응답: 서버가 요청에 대한 응답을 반환합니다.</p>
<p>데이터 전송: HTML, CSS, 이미지 등 웹 페이지를 구성하는 요소들이 전송됩니다.</p>
<h1 id="https의-설명과-특징">HTTPS의 설명과 특징</h1>
<hr>
<h2 id="https란">HTTPS란?</h2>
<p>HTTPS(HyperText Transfer Protocol Secure)는 HTTP에 보안 계층을 추가한 프로토콜로, SSL/TLS를 통해 데이터 암호화 및 서버 인증을 제공합니다. </p>
<h3 id="https의-주요-특징">HTTPS의 주요 특징</h3>
<p>데이터 암호화: 전송되는 데이터가 암호화되어 제3자가 내용을 볼 수 없습니다.</p>
<p>서버 인증: SSL/TLS 인증서를 통해 서버의 신원을 확인합니다.</p>
<p>데이터 무결성: 데이터가 전송 중 변경되지 않았음을 보장합니다.
배움이 즐거운 개발자</p>
<h3 id="https-통신-과정">HTTPS 통신 과정</h3>
<p>브라우저가 서버에 연결 요청: HTTPS를 통해 서버에 접속합니다.</p>
<p>SSL/TLS 핸드셰이크: 서버는 인증서를 제공하고, 클라이언트는 이를 검증합니다.</p>
<p>세션 키 생성: 클라이언트와 서버는 대칭키를 생성하여 데이터를 암호화합니다.</p>
<p>암호화된 데이터 전송: 세션 키를 사용하여 안전하게 데이터를 주고받습니다.</p>
]]></description>
        </item>
    </channel>
</rss>