<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hyeoni_.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 29 Sep 2025 15:50:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hyeoni_.log</title>
            <url>https://velog.velcdn.com/images/hyeoni_/profile/d599d3ac-cfc0-49c3-95a5-6658551a8e8f/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hyeoni_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyeoni_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[📑 DRF와 Node.js]]></title>
            <link>https://velog.io/@hyeoni_/DRF-Node.js</link>
            <guid>https://velog.io/@hyeoni_/DRF-Node.js</guid>
            <pubDate>Mon, 29 Sep 2025 15:50:55 GMT</pubDate>
            <description><![CDATA[<h1 id="🏗️-djangodrf-vs-nodejsexpress-개념-비교">🏗️ Django/DRF vs Node.js/Express 개념 비교</h1>
<table>
<thead>
<tr>
<th>Django/DRF</th>
<th>Node.js/Express</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>프로젝트 생성 (<code>django-admin startproject</code>)</td>
<td>직접 폴더/파일 구성</td>
<td>Node.js는 프레임워크가 가볍기 때문에 스캐폴딩 도구가 거의 없음</td>
</tr>
<tr>
<td>settings.py</td>
<td><code>dotenv</code> + 환경 변수</td>
<td>설정 관리도 직접 라이브러리(<code>dotenv</code>)로 구성</td>
</tr>
<tr>
<td>models.py (ORM)</td>
<td>Sequelize, Prisma, Mongoose</td>
<td>ORM/ODM을 직접 선택해서 설치해야 함</td>
</tr>
<tr>
<td>views.py (APIView, ViewSet)</td>
<td>Router + Controller 함수</td>
<td>URL 라우팅과 로직을 분리해서 구현</td>
</tr>
<tr>
<td>serializers.py</td>
<td>없음 (대신 DTO, class-validator 등)</td>
<td>직렬화/검증은 직접 구현하거나 라이브러리 사용</td>
</tr>
<tr>
<td>urls.py</td>
<td>Express Router</td>
<td>요청 라우팅 담당</td>
</tr>
<tr>
<td>manage.py runserver</td>
<td><code>node index.js</code></td>
<td>서버 실행</td>
</tr>
<tr>
<td>DRF 기본 제공 기능 (인증, 페이징, 필터 등)</td>
<td>직접 구현 + 미들웨어</td>
<td>필요한 기능은 라이브러리로 가져다 씀</td>
</tr>
</tbody></table>
<p>👉 즉, Django는 “배터리 포함” 철학이라 기본 기능이 다 들어있는데,
Node.js는 “필요한 라이브러리 골라 쓰기” 철학이라 훨씬 자유도가 높습니다.</p>
<hr>
<h2 id="🚀-nodejs-백엔드-개발-흐름-expressjs-기준">🚀 Node.js 백엔드 개발 흐름 (Express.js 기준)</h2>
<h3 id="1-프로젝트-초기화">1. 프로젝트 초기화</h3>
<pre><code class="language-bash">mkdir node-api
cd node-api
npm init -y   # package.json 생성
npm install express dotenv</code></pre>
<hr>
<h3 id="2-서버-실행-indexjs">2. 서버 실행 (index.js)</h3>
<pre><code class="language-bash">const express = require(&quot;express&quot;);
const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json()); // JSON 파싱 미들웨어

app.get(&quot;/&quot;, (req, res) =&gt; {
  res.send(&quot;Hello from Node.js API&quot;);
});

app.listen(PORT, () =&gt; {
  console.log(`🚀 Server running on http://localhost:${PORT}`);
});</code></pre>
<p>👉 Django에서 python manage.py runserver 하는 것과 같은 단계입니다.</p>
<hr>
<h3 id="3-라우팅과-컨트롤러">3. 라우팅과 컨트롤러</h3>
<p>Django는 <strong>urls.py + views.py</strong>로 나누죠.
Express에서는 보통 <strong>Router + Controller</strong>로 분리합니다.</p>
<pre><code class="language-js">// routes/todoRoutes.js
const express = require(&quot;express&quot;);
const router = express.Router();
const { getTodos, createTodo } = require(&quot;../controllers/todoController&quot;);

router.get(&quot;/&quot;, getTodos);
router.post(&quot;/&quot;, createTodo);

module.exports = router;</code></pre>
<pre><code class="language-js">// controllers/todoController.js
let todos = [];

exports.getTodos = (req, res) =&gt; {
  res.json(todos);
};

exports.createTodo = (req, res) =&gt; {
  const { task } = req.body;
  const newTodo = { id: Date.now(), task, done: false };
  todos.push(newTodo);
  res.status(201).json(newTodo);
};</code></pre>
<pre><code class="language-js">// index.js
const todoRoutes = require(&quot;./routes/todoRoutes&quot;);
app.use(&quot;/todos&quot;, todoRoutes);</code></pre>
<p>👉 Django의 urlpatterns + APIView 조합을 Express 버전으로 나눈 거예요.</p>
<hr>
<h3 id="4-데이터베이스-연동">4. 데이터베이스 연동</h3>
<p>Django는 ORM이 내장되어 있지만, Node.js는 직접 선택해야 합니다.</p>
<ul>
<li><strong>MongoDB</strong> → mongoose (ODM)</li>
<li><strong>PostgreSQL/MySQL</strong> → Sequelize 또는 Prisma (ORM)
예: Prisma (PostgreSQL)<pre><code class="language-bash">npm install prisma @prisma/client
npx prisma init</code></pre>
</li>
</ul>
<p>→ schema.prisma 파일에서 모델 정의 → npx prisma migrate dev 로 DB 반영
👉 Django의 models.py + makemigrations + migrate와 거의 비슷한 흐름입니다.</p>
<hr>
<h3 id="5-인증">5. 인증</h3>
<ul>
<li>Django REST Framework: JWTAuthentication, SessionAuthentication 내장</li>
<li>Node.js: 직접 구현 + 라이브러리(jsonwebtoken, passport.js)</li>
</ul>
<p>예:</p>
<pre><code class="language-js">const jwt = require(&quot;jsonwebtoken&quot;);

app.post(&quot;/login&quot;, (req, res) =&gt; {
  const { username } = req.body;
  const token = jwt.sign({ username }, process.env.JWT_SECRET, { expiresIn: &quot;1h&quot; });
  res.json({ token });
});</code></pre>
<p>👉 DRF의 JWT 토큰 발급과 같은 개념</p>
<hr>
<h3 id="6-미들웨어">6. 미들웨어</h3>
<p>Django의 MIDDLEWARE 설정과 비슷합니다.
Express에서는 <strong>app.use()</strong> 로 등록합니다.</p>
<p>예:</p>
<pre><code class="language-js">const cors = require(&quot;cors&quot;);
app.use(cors());</code></pre>
<hr>
<h3 id="✅-정리">✅ 정리</h3>
<ul>
<li>Node.js 백엔드는 Django처럼 다 갖춰져 있지 않고, 라이브러리 조합으로 완성합니다.</li>
<li>Express = DRF의 최소한만 남겨둔 버전이라고 보면 이해하기 쉽습니다.</li>
<li>큰 차이는 구조를 직접 설계해야 하고, ORM/인증/직렬화 등도 직접 선택해야 한다는 점이에요.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[💻 Node.js API 서버 TIP]]></title>
            <link>https://velog.io/@hyeoni_/Node.js-API-TIP</link>
            <guid>https://velog.io/@hyeoni_/Node.js-API-TIP</guid>
            <pubDate>Mon, 29 Sep 2025 15:36:07 GMT</pubDate>
            <description><![CDATA[<p>Node.js로 API 서버를 개발하다 보면 누구나 한 번쯤 Express.js로 “Hello World”를 찍어본 경험이 있을 겁니다. 하지만 실무에서는 단순히 서버를 띄우는 것보다 구조화, 확장성, 유지보수성이 훨씬 중요합니다.</p>
<p>이번 글에서는 간단한 Todo API 예시를 바탕으로, 실무에서 자주 부딪히는 문제와 그 해결 팁을 공유하겠습니다.</p>
<hr>
<h3 id="1-express-서버는-최소한의-구조를-갖추자">1. Express 서버는 최소한의 구조를 갖추자</h3>
<p>초보 단계에서는 모든 코드를 index.js에 몰아 넣곤 합니다. 하지만 규모가 커지면 유지보수가 힘들어집니다.</p>
<p>👉 추천하는 최소 구조는 다음과 같습니다.</p>
<pre><code class="language-bash">/src
  /routes
    todoRoutes.js
  /controllers
    todoController.js
  index.js</code></pre>
<ul>
<li>Routes: URL과 메서드 정의</li>
<li>Controllers: 실제 비즈니스 로직</li>
</ul>
<hr>
<h3 id="2-예외-처리는-반드시-해라">2. 예외 처리는 반드시 해라</h3>
<p>실무에서는 없는 리소스를 요청하거나, 잘못된 요청 바디가 들어오는 경우가 많습니다.</p>
<pre><code class="language-js">// 예외 처리 없는 경우 → 서버 다운 가능
app.post(&quot;/todos&quot;, (req, res) =&gt; {
  const { task } = req.body;
  if (!task) return res.status(400).json({ message: &quot;Task is required&quot; });
});</code></pre>
<p>👉 항상 요청 값 검증(validation)을 하고, 에러 미들웨어를 만들어 공통 처리하는 게 좋습니다.</p>
<hr>
<h3 id="3-데이터베이스-연동-고려하기">3. 데이터베이스 연동 고려하기</h3>
<p>실습에서는 메모리에 데이터를 저장했지만, 실무에서는 DB 연결이 필수입니다.
Node.js에서 가장 많이 쓰는 DB는 <strong>MongoDB(Mongoose)</strong>, <strong>PostgreSQL(Sequelize/Prisma)</strong> 입니다.</p>
<ul>
<li>작은 프로젝트 → MongoDB</li>
<li>트랜잭션이 중요한 프로젝트 → PostgreSQL/MySQL</li>
</ul>
<hr>
<h3 id="4-환경-변수와-보안">4. 환경 변수와 보안</h3>
<p><strong>절대 DB URL, API Key 등을 코드에 직접 쓰지 마세요.</strong>
.env 파일을 두고 dotenv 패키지를 사용하면 안전하게 관리할 수 있습니다.</p>
<pre><code class="language-js">require(&quot;dotenv&quot;).config();
const DB_URL = process.env.DB_URL;</code></pre>
<hr>
<h3 id="5-확장성-있는-서버를-위해">5. 확장성 있는 서버를 위해</h3>
<ul>
<li>미들웨어 분리 (인증, 로깅, CORS)</li>
<li><em>async/await</em> 기반의 에러 핸들링 패턴</li>
<li>Docker로 배포 고려</li>
<li>테스트 코드 (Jest, Supertest) 작성</li>
</ul>
<hr>
<h3 id="⭐-결론">⭐ 결론</h3>
<p>Node.js와 Express로 API 서버를 만드는 건 단순합니다. 하지만 실무에서는 <strong>구조화, 보안, 확장성</strong>이 차이를 만듭니다.
처음에는 간단한 예제를 만들어보고, 점차 DB 연결 → 인증 → 배포까지 경험을 넓혀 나간다면 빠르게 성장할 수 있을 겁니다. 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤖 Node.js로 나만의 REST API 만들기]]></title>
            <link>https://velog.io/@hyeoni_/node.js-base</link>
            <guid>https://velog.io/@hyeoni_/node.js-base</guid>
            <pubDate>Mon, 29 Sep 2025 15:26:02 GMT</pubDate>
            <description><![CDATA[<p>“백엔드 개발, 어디서부터 시작해야 하지?”
처음 접하는 분들에게 Node.js는 훌륭한 선택입니다. 자바스크립트 하나로 프론트엔드와 백엔드를 모두 다룰 수 있고, 가볍고 빠른 서버를 쉽게 만들 수 있기 때문입니다.</p>
<p>이번 튜토리얼에서는 할 일(To-do) 관리 API를 직접 만들어보며 REST API의 기초를 익혀봅시다.</p>
<hr>
<h3 id="1단계-👉🏻-프로젝트-초기화">1단계 👉🏻 프로젝트 초기화</h3>
<p>먼저 프로젝트 폴더를 만들고 Node.js 환경을 설정합니다.</p>
<pre><code class="language-bash">mkdir node-api-tutorial
cd node-api-tutorial
npm init -y
npm install express</code></pre>
<ul>
<li>npm init -y: 기본 package.json 생성</li>
<li>npm install express: 서버 구축을 위한 Express.js 설치</li>
</ul>
<hr>
<h3 id="2단계-👉🏻-서버-실행하기">2단계 👉🏻 서버 실행하기</h3>
<p>index.js 파일을 만들고 간단한 서버를 띄워봅니다.</p>
<pre><code class="language-bash">const express = require(&quot;express&quot;);
const app = express();
const PORT = 3000;

app.use(express.json());

app.get(&quot;/&quot;, (req, res) =&gt; {
  res.send(&quot;Hello Node.js API!&quot;);
});

app.listen(PORT, () =&gt; {
  console.log(`🚀 Server running at http://localhost:${PORT}`);
});</code></pre>
<ul>
<li>이제 터미널에서 node index.js 실행 후 브라우저에서 <a href="http://localhost:3000">http://localhost:3000</a> 에 접속하면 메시지를 확인할 수 있습니다.</li>
</ul>
<hr>
<h3 id="3단계-👉🏻-to-do-api-만들기">3단계 👉🏻 To-do API 만들기</h3>
<p>이제 본격적으로 API 엔드포인트를 작성해봅시다.</p>
<pre><code class="language-bash">let todos = [];

// 목록 조회
app.get(&quot;/todos&quot;, (req, res) =&gt; {
  res.json(todos);
});

// 추가
app.post(&quot;/todos&quot;, (req, res) =&gt; {
  const { task } = req.body;
  const newTodo = { id: Date.now(), task, done: false };
  todos.push(newTodo);
  res.status(201).json(newTodo);
});

// 완료 처리
app.patch(&quot;/todos/:id&quot;, (req, res) =&gt; {
  const { id } = req.params;
  const todo = todos.find((t) =&gt; t.id == id);
  if (!todo) return res.status(404).json({ message: &quot;Not found&quot; });

  todo.done = true;
  res.json(todo);
});

// 삭제
app.delete(&quot;/todos/:id&quot;, (req, res) =&gt; {
  const { id } = req.params;
  todos = todos.filter((t) =&gt; t.id != id);
  res.json({ message: &quot;Deleted&quot; });
});</code></pre>
<hr>
<h3 id="4단계-👉🏻-테스트하기">4단계 👉🏻 테스트하기</h3>
<p>Postman이나 cURL을 사용해 요청을 보낼 수 있습니다.</p>
<ul>
<li>GET /todos → 전체 목록 조회</li>
<li>POST /todos → { &quot;task&quot;: &quot;블로그 작성&quot; } 추가</li>
<li>PATCH /todos/:id → 특정 항목 완료</li>
<li>DELETE /todos/:id → 특정 항목 삭제</li>
</ul>
<hr>
<h3 id="💡-마무리">💡 마무리</h3>
<p>이렇게 간단한 To-do API 서버를 완성했습니다.
실제 서비스에선 데이터베이스 연결, 에러 처리, 인증/인가(JWT) 등을 추가하면 훨씬 완성도 있는 서버가 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Django REST Framework ListAPIView 완벽 가이드]]></title>
            <link>https://velog.io/@hyeoni_/DRFListAPIView-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyeoni_/DRFListAPIView-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 16 Jul 2025 01:23:44 GMT</pubDate>
            <description><![CDATA[<h2 id="🔍-들어가며">🔍 들어가며</h2>
<p>웹 개발에서 API(Application Programming Interface)는 마치 레스토랑의 메뉴판과 같습니다. 고객(클라이언트)이 원하는 음식(데이터)을 주문할 수 있는 방법을 제공하죠. Django REST Framework(DRF)의 ListAPIView는 이러한 API 중에서도 특히 &quot;목록을 보여주는&quot; 기능을 담당하는 강력한 도구입니다.</p>
<h2 id="listapiview란-무엇인가">ListAPIView란 무엇인가?</h2>
<p>ListAPIView는 Django REST Framework에서 제공하는 <strong>제네릭 뷰(Generic View)</strong> 중 하나입니다. 이름에서 알 수 있듯이, 데이터베이스에 저장된 여러 개의 객체들을 <strong>목록 형태로 조회</strong>하는 기능을 제공합니다.</p>
<h3 id="실생활-예시로-이해하기">실생활 예시로 이해하기</h3>
<p>온라인 쇼핑몰을 생각해보세요:</p>
<ul>
<li>상품 목록 페이지 → ListAPIView가 상품 데이터들을 JSON 형태로 제공</li>
<li>게시글 목록 페이지 → ListAPIView가 게시글 데이터들을 제공</li>
<li>사용자 목록 페이지 → ListAPIView가 사용자 데이터들을 제공</li>
</ul>
<h2 id="기본-사용법">기본 사용법</h2>
<h3 id="1-기본-설정">1. 기본 설정</h3>
<p>먼저 Django REST Framework가 설치되어 있어야 합니다:</p>
<pre><code class="language-bash">pip install djangorestframework</code></pre>
<p><code>settings.py</code>에 DRF를 추가:</p>
<pre><code class="language-python">INSTALLED_APPS = [
    # ... 기존 앱들
    &#39;rest_framework&#39;,
    &#39;your_app&#39;,
]</code></pre>
<h3 id="2-모델-생성">2. 모델 생성</h3>
<p>블로그 포스트를 예시로 사용하겠습니다:</p>
<pre><code class="language-python"># models.py
from django.db import models
from django.contrib.auth.models import User

class BlogPost(models.Model):
    title = models.CharField(max_length=200, verbose_name=&quot;제목&quot;)
    content = models.TextField(verbose_name=&quot;내용&quot;)
    author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=&quot;작성자&quot;)
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=&quot;작성일&quot;)
    updated_at = models.DateTimeField(auto_now=True, verbose_name=&quot;수정일&quot;)
    is_published = models.BooleanField(default=True, verbose_name=&quot;공개 여부&quot;)

    class Meta:
        ordering = [&#39;-created_at&#39;]  # 최신순 정렬
        verbose_name = &quot;블로그 포스트&quot;
        verbose_name_plural = &quot;블로그 포스트들&quot;

    def __str__(self):
        return self.title</code></pre>
<h3 id="3-시리얼라이저-생성">3. 시리얼라이저 생성</h3>
<p>시리얼라이저는 <strong>번역기</strong> 역할을 합니다. 파이썬 객체를 JSON으로, JSON을 파이썬 객체로 변환해줍니다:</p>
<pre><code class="language-python"># serializers.py
from rest_framework import serializers
from .models import BlogPost

class BlogPostSerializer(serializers.ModelSerializer):
    author_name = serializers.CharField(source=&#39;author.username&#39;, read_only=True)

    class Meta:
        model = BlogPost
        fields = [&#39;id&#39;, &#39;title&#39;, &#39;content&#39;, &#39;author_name&#39;, &#39;created_at&#39;, &#39;updated_at&#39;, &#39;is_published&#39;]
        read_only_fields = [&#39;created_at&#39;, &#39;updated_at&#39;]</code></pre>
<h3 id="4-listapiview-구현">4. ListAPIView 구현</h3>
<p>이제 핵심인 ListAPIView를 구현해보겠습니다:</p>
<pre><code class="language-python"># views.py
from rest_framework.generics import ListAPIView
from rest_framework.filters import SearchFilter, OrderingFilter
from django_filters.rest_framework import DjangoFilterBackend
from .models import BlogPost
from .serializers import BlogPostSerializer

class BlogPostListView(ListAPIView):
    &quot;&quot;&quot;
    블로그 포스트 목록을 제공하는 API View
    &quot;&quot;&quot;
    queryset = BlogPost.objects.filter(is_published=True)
    serializer_class = BlogPostSerializer
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = [&#39;author&#39;, &#39;is_published&#39;]
    search_fields = [&#39;title&#39;, &#39;content&#39;]
    ordering_fields = [&#39;created_at&#39;, &#39;updated_at&#39;, &#39;title&#39;]
    ordering = [&#39;-created_at&#39;]  # 기본 정렬: 최신순</code></pre>
<h3 id="5-url-연결">5. URL 연결</h3>
<pre><code class="language-python"># urls.py
from django.urls import path
from .views import BlogPostListView

urlpatterns = [
    path(&#39;api/posts/&#39;, BlogPostListView.as_view(), name=&#39;blog-post-list&#39;),
]</code></pre>
<h2 id="고급-기능들">고급 기능들</h2>
<h3 id="1-커스텀-필터링">1. 커스텀 필터링</h3>
<p>때로는 기본 필터링으로는 부족할 수 있습니다:</p>
<pre><code class="language-python">from rest_framework.generics import ListAPIView
from django.db.models import Q
from datetime import datetime, timedelta

class BlogPostListView(ListAPIView):
    serializer_class = BlogPostSerializer

    def get_queryset(self):
        &quot;&quot;&quot;
        커스텀 쿼리셋 로직
        &quot;&quot;&quot;
        queryset = BlogPost.objects.filter(is_published=True)

        # URL 파라미터 처리
        author_id = self.request.query_params.get(&#39;author_id&#39;)
        if author_id:
            queryset = queryset.filter(author_id=author_id)

        # 최근 포스트만 필터링
        recent = self.request.query_params.get(&#39;recent&#39;)
        if recent == &#39;true&#39;:
            week_ago = datetime.now() - timedelta(days=7)
            queryset = queryset.filter(created_at__gte=week_ago)

        # 키워드 검색
        keyword = self.request.query_params.get(&#39;keyword&#39;)
        if keyword:
            queryset = queryset.filter(
                Q(title__icontains=keyword) | 
                Q(content__icontains=keyword)
            )

        return queryset</code></pre>
<h3 id="2-페이지네이션-설정">2. 페이지네이션 설정</h3>
<p>대량의 데이터를 효율적으로 처리하기 위해 페이지네이션을 설정합니다:</p>
<pre><code class="language-python"># settings.py
REST_FRAMEWORK = {
    &#39;DEFAULT_PAGINATION_CLASS&#39;: &#39;rest_framework.pagination.PageNumberPagination&#39;,
    &#39;PAGE_SIZE&#39;: 10
}</code></pre>
<p>커스텀 페이지네이션:</p>
<pre><code class="language-python"># pagination.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

class CustomPageNumberPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = &#39;page_size&#39;
    max_page_size = 100

    def get_paginated_response(self, data):
        return Response({
            &#39;pagination&#39;: {
                &#39;next&#39;: self.get_next_link(),
                &#39;previous&#39;: self.get_previous_link(),
                &#39;count&#39;: self.page.paginator.count,
                &#39;total_pages&#39;: self.page.paginator.num_pages,
                &#39;current_page&#39;: self.page.number,
                &#39;page_size&#39;: self.page_size
            },
            &#39;results&#39;: data
        })

# views.py에서 사용
class BlogPostListView(ListAPIView):
    queryset = BlogPost.objects.filter(is_published=True)
    serializer_class = BlogPostSerializer
    pagination_class = CustomPageNumberPagination</code></pre>
<h3 id="3-권한-설정">3. 권한 설정</h3>
<p>누가 API에 접근할 수 있는지 제어합니다:</p>
<pre><code class="language-python">from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly

class BlogPostListView(ListAPIView):
    queryset = BlogPost.objects.filter(is_published=True)
    serializer_class = BlogPostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]  # 읽기는 누구나, 쓰기는 인증된 사용자만</code></pre>
<h3 id="4-캐싱-적용">4. 캐싱 적용</h3>
<p>성능 향상을 위해 캐싱을 적용할 수 있습니다:</p>
<pre><code class="language-python">from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page

@method_decorator(cache_page(60 * 15), name=&#39;get&#39;)  # 15분 캐싱
class BlogPostListView(ListAPIView):
    queryset = BlogPost.objects.filter(is_published=True)
    serializer_class = BlogPostSerializer</code></pre>
<h2 id="실제-api-응답-예시">실제 API 응답 예시</h2>
<p>위에서 만든 API를 호출하면 다음과 같은 JSON 응답을 받을 수 있습니다:</p>
<pre><code class="language-json">{
    &quot;pagination&quot;: {
        &quot;next&quot;: &quot;http://localhost:8000/api/posts/?page=2&quot;,
        &quot;previous&quot;: null,
        &quot;count&quot;: 45,
        &quot;total_pages&quot;: 3,
        &quot;current_page&quot;: 1,
        &quot;page_size&quot;: 20
    },
    &quot;results&quot;: [
        {
            &quot;id&quot;: 1,
            &quot;title&quot;: &quot;Django REST Framework 시작하기&quot;,
            &quot;content&quot;: &quot;Django REST Framework는 강력한 웹 API 프레임워크입니다...&quot;,
            &quot;author_name&quot;: &quot;김개발&quot;,
            &quot;created_at&quot;: &quot;2024-01-15T10:30:00Z&quot;,
            &quot;updated_at&quot;: &quot;2024-01-15T10:30:00Z&quot;,
            &quot;is_published&quot;: true
        },
        {
            &quot;id&quot;: 2,
            &quot;title&quot;: &quot;Python 기초 문법&quot;,
            &quot;content&quot;: &quot;Python은 배우기 쉬운 프로그래밍 언어입니다...&quot;,
            &quot;author_name&quot;: &quot;이프로그래머&quot;,
            &quot;created_at&quot;: &quot;2024-01-14T15:45:00Z&quot;,
            &quot;updated_at&quot;: &quot;2024-01-14T15:45:00Z&quot;,
            &quot;is_published&quot;: true
        }
    ]
}</code></pre>
<h2 id="다양한-사용-예시">다양한 사용 예시</h2>
<h3 id="1-카테고리별-필터링">1. 카테고리별 필터링</h3>
<pre><code class="language-python">class BlogPostListView(ListAPIView):
    serializer_class = BlogPostSerializer

    def get_queryset(self):
        category = self.kwargs.get(&#39;category&#39;)
        if category:
            return BlogPost.objects.filter(category=category, is_published=True)
        return BlogPost.objects.filter(is_published=True)

# urls.py
path(&#39;api/posts/category/&lt;str:category&gt;/&#39;, BlogPostListView.as_view())</code></pre>
<h3 id="2-특정-작성자의-포스트만-조회">2. 특정 작성자의 포스트만 조회</h3>
<pre><code class="language-python">class AuthorPostListView(ListAPIView):
    serializer_class = BlogPostSerializer

    def get_queryset(self):
        author_id = self.kwargs[&#39;author_id&#39;]
        return BlogPost.objects.filter(author_id=author_id, is_published=True)

# urls.py
path(&#39;api/authors/&lt;int:author_id&gt;/posts/&#39;, AuthorPostListView.as_view())</code></pre>
<h2 id="주의사항-및-베스트-프랙티스">주의사항 및 베스트 프랙티스</h2>
<h3 id="1-성능-최적화">1. 성능 최적화</h3>
<pre><code class="language-python">class BlogPostListView(ListAPIView):
    # select_related로 연관 객체 한 번에 조회
    queryset = BlogPost.objects.select_related(&#39;author&#39;).filter(is_published=True)
    serializer_class = BlogPostSerializer</code></pre>
<h3 id="2-에러-처리">2. 에러 처리</h3>
<pre><code class="language-python">from rest_framework.exceptions import ValidationError

class BlogPostListView(ListAPIView):
    serializer_class = BlogPostSerializer

    def get_queryset(self):
        try:
            return BlogPost.objects.filter(is_published=True)
        except Exception as e:
            raise ValidationError(f&quot;데이터를 불러오는 중 오류가 발생했습니다: {str(e)}&quot;)</code></pre>
<h3 id="3-보안-고려사항">3. 보안 고려사항</h3>
<pre><code class="language-python">class BlogPostListView(ListAPIView):
    serializer_class = BlogPostSerializer

    def get_queryset(self):
        # 민감한 정보가 포함된 필드는 제외
        return BlogPost.objects.filter(is_published=True).exclude(is_draft=True)</code></pre>
<h2 id="마무리">마무리</h2>
<p>ListAPIView는 Django REST Framework에서 목록 조회 API를 구현하는 가장 간단하고 효율적인 방법입니다. 기본적인 CRUD 작업 중 &#39;Read&#39; 기능을 담당하며, 필터링, 검색, 정렬, 페이지네이션 등 다양한 기능을 쉽게 구현할 수 있습니다.</p>
<p>핵심 포인트:</p>
<ul>
<li><strong>간단한 구현</strong>: 몇 줄의 코드로 완전한 API 구현 가능</li>
<li><strong>높은 확장성</strong>: 커스텀 로직 추가 용이</li>
<li><strong>풍부한 기능</strong>: 필터링, 검색, 정렬, 페이지네이션 내장</li>
<li><strong>성능 최적화</strong>: 캐싱, 쿼리 최적화 등 다양한 최적화 기법 적용 가능</li>
</ul>
<p>ListAPIView를 마스터하면 웹 API 개발에서 목록 조회 기능을 빠르고 안정적으로 구현할 수 있습니다. 다음 단계로는 CreateAPIView, UpdateAPIView, DestroyAPIView 등 다른 제네릭 뷰들도 학습해보시기 바랍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🚀 Django로 AI 기능이 있는 스마트 블로그 만들기]]></title>
            <link>https://velog.io/@hyeoni_/Django-AISmartBlog</link>
            <guid>https://velog.io/@hyeoni_/Django-AISmartBlog</guid>
            <pubDate>Thu, 10 Jul 2025 07:24:28 GMT</pubDate>
            <description><![CDATA[<h2 id="📋-프로젝트-개요">📋 프로젝트 개요</h2>
<p><strong>SmartBlog</strong>는 OpenAI API를 활용한 AI 기능이 탑재된 Django 블로그 플랫폼입니다. 사용자가 더 쉽고 효율적으로 블로그를 작성할 수 있도록 다양한 AI 어시스턴트 기능을 제공합니다.</p>
<h3 id="🎯-주요-목표">🎯 주요 목표</h3>
<ul>
<li><strong>Django 프레임워크</strong> 숙련도 향상</li>
<li><strong>외부 API 연동</strong> 경험 (OpenAI API)</li>
<li><strong>풀스택 개발</strong> 역량 강화</li>
<li><strong>사용자 경험(UX)</strong> 개선</li>
</ul>
<hr>
<h2 id="✨-주요-기능">✨ 주요 기능</h2>
<h3 id="🤖-ai-어시스턴트-기능">🤖 AI 어시스턴트 기능</h3>
<ul>
<li><strong>제목 추천</strong>: 글 내용을 분석하여 매력적인 제목 4개 제안</li>
<li><strong>자동 완성</strong>: 작성 중인 글을 자연스럽게 이어서 작성</li>
<li><strong>태그 추천</strong>: 글의 주제에 맞는 태그 5개 자동 생성</li>
<li><strong>요약 생성</strong>: 긴 글을 200자 이내로 핵심 요약</li>
</ul>
<h3 id="📝-블로그-핵심-기능">📝 블로그 핵심 기능</h3>
<ul>
<li><strong>게시글 CRUD</strong>: 작성, 조회, 수정, 삭제</li>
<li><strong>댓글 시스템</strong>: 댓글 및 대댓글 기능</li>
<li><strong>좋아요 기능</strong>: Ajax를 활용한 실시간 좋아요</li>
<li><strong>검색 및 필터링</strong>: 제목, 내용, 태그로 검색 가능</li>
</ul>
<h3 id="👥-사용자-관리">👥 사용자 관리</h3>
<ul>
<li><strong>회원가입/로그인</strong>: Django 기본 인증 시스템 활용</li>
<li><strong>프로필 관리</strong>: 사용자 정보 수정</li>
<li><strong>팔로우 시스템</strong>: 다른 사용자 팔로우/언팔로우</li>
<li><strong>AI 사용량 추적</strong>: 개인별 AI 기능 사용 통계</li>
</ul>
<hr>
<h2 id="🛠-기술-스택">🛠 기술 스택</h2>
<h3 id="backend">Backend</h3>
<ul>
<li><strong>Django 5.2</strong>: Python 웹 프레임워크</li>
<li><strong>SQLite</strong>: 개발용 데이터베이스</li>
<li><strong>Django ORM</strong>: 데이터베이스 추상화</li>
</ul>
<h3 id="frontend">Frontend</h3>
<ul>
<li><strong>HTML5/CSS3</strong>: 마크업 및 스타일링</li>
<li><strong>JavaScript</strong>: 동적 기능 구현</li>
<li><strong>Bootstrap</strong>: 반응형 UI 프레임워크</li>
</ul>
<h3 id="ai-integration">AI Integration</h3>
<ul>
<li><strong>OpenAI API</strong>: GPT-4o-mini 모델 활용</li>
<li><strong>Custom AI Service</strong>: API 래핑 및 에러 처리</li>
</ul>
<h3 id="tools--others">Tools &amp; Others</h3>
<ul>
<li><strong>Git</strong>: 버전 관리</li>
<li><strong>django-environ</strong>: 환경변수 관리</li>
<li><strong>Logging</strong>: 디버깅 및 모니터링</li>
</ul>
<hr>
<h2 id="🏗-프로젝트-구조">🏗 프로젝트 구조</h2>
<pre><code>smartblog/
├── accounts/           # 사용자 관리 앱
│   ├── models.py      # CustomUser, Follow 모델
│   ├── views.py       # 인증, 프로필, 팔로우 뷰
│   └── admin.py       # 관리자 설정
├── blog/              # 블로그 메인 앱
│   ├── models.py      # Post, Comment, Like, Tag, AIUsageLog 모델
│   ├── views.py       # 게시글, 댓글, 좋아요 뷰
│   ├── ai_views.py    # AI 기능 API 뷰
│   └── ai_service.py  # OpenAI API 서비스 클래스
├── templates/         # HTML 템플릿
├── static/           # CSS, JS, 이미지 파일
└── media/            # 사용자 업로드 파일</code></pre><hr>
<h2 id="💡-핵심-구현-내용">💡 핵심 구현 내용</h2>
<h3 id="1-커스텀-사용자-모델">1. 커스텀 사용자 모델</h3>
<pre><code class="language-python">class CustomUser(AbstractUser):
    bio = models.TextField(max_length=500, blank=True)
    ai_usage_count = models.IntegerField(default=0)

    def get_follower_count(self):
        return self.followers.count()

    def toggle_follow(self, user):
        follow, created = Follow.objects.get_or_create(
            follower=self, following=user
        )
        if not created:
            follow.delete()
            return False, user.get_follower_count()
        return True, user.get_follower_count()</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li>Django의 <code>AbstractUser</code>를 확장하여 커스텀 필드 추가</li>
<li>팔로우 관계를 위한 <code>ManyToMany</code> 관계 구현</li>
<li>AI 사용량 추적을 위한 <code>ai_usage_count</code> 필드</li>
</ul>
<h3 id="2-ai-서비스-클래스">2. AI 서비스 클래스</h3>
<pre><code class="language-python">class OpenAIService:
    def __init__(self):
        self.client = OpenAI(api_key=settings.OPENAI_API_KEY)
        self.model = &quot;gpt-4o-mini&quot;

    def generate_title_suggestions(self, content: str, count: int = 5):
        messages = [
            {
                &quot;role&quot;: &quot;system&quot;,
                &quot;content&quot;: &quot;한국어 블로그 제목을 추천하는 전문가입니다.&quot;
            },
            {
                &quot;role&quot;: &quot;user&quot;, 
                &quot;content&quot;: f&quot;다음 글에 대한 {count}개의 제목을 추천해주세요: {content}&quot;
            }
        ]
        response = self._make_request(messages)
        return self._parse_titles(response)</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li><strong>싱글톤 패턴</strong>으로 API 클라이언트 관리</li>
<li><strong>에러 처리</strong>: API 한도 초과, 인증 실패 등 예외 상황 대응</li>
<li><strong>더미 모드</strong>: API 키가 없을 때 테스트용 응답 제공</li>
<li><strong>로깅</strong>: API 사용량 및 에러 추적</li>
</ul>
<h3 id="3-ajax를-활용한-실시간-기능">3. Ajax를 활용한 실시간 기능</h3>
<pre><code class="language-javascript">// 좋아요 토글
function toggleLike(postId) {
    fetch(`/blog/like/${postId}/`, {
        method: &#39;POST&#39;,
        headers: {
            &#39;X-CSRFToken&#39;: getCookie(&#39;csrftoken&#39;),
            &#39;Content-Type&#39;: &#39;application/json&#39;
        }
    })
    .then(response =&gt; response.json())
    .then(data =&gt; {
        if (data.success) {
            updateLikeButton(postId, data.is_liked, data.like_count);
            showMessage(data.message, &#39;success&#39;);
        }
    });
}</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li><strong>페이지 새로고침 없이</strong> 좋아요, 댓글, AI 기능 동작</li>
<li><strong>CSRF 토큰</strong> 처리로 보안 강화</li>
<li><strong>사용자 피드백</strong>을 위한 실시간 메시지 표시</li>
</ul>
<h3 id="4-효율적인-데이터베이스-쿼리">4. 효율적인 데이터베이스 쿼리</h3>
<pre><code class="language-python">def get_queryset(self):
    return Post.objects.select_related(&quot;author&quot;)\
                      .prefetch_related(&quot;tags&quot;, &quot;likes&quot;)\
                      .annotate(likes_count=Count(&#39;likes&#39;))\
                      .order_by(&quot;-created_at&quot;)</code></pre>
<p><strong>특징:</strong></p>
<ul>
<li><strong><code>select_related</code></strong>: 외래키 관계 최적화</li>
<li><strong><code>prefetch_related</code></strong>: 다대다 관계 최적화</li>
<li><strong><code>annotate</code></strong>: 집계 함수로 좋아요 수 계산</li>
</ul>
<hr>
<h2 id="🎨-uiux-고민">🎨 UI/UX 고민</h2>
<h3 id="1-반응형-디자인">1. 반응형 디자인</h3>
<ul>
<li><strong>Bootstrap Grid System</strong>을 활용한 모바일 친화적 레이아웃</li>
<li><strong>미디어 쿼리</strong>로 다양한 화면 크기 지원</li>
</ul>
<h3 id="2-사용자-경험-개선">2. 사용자 경험 개선</h3>
<ul>
<li><strong>로딩 스피너</strong>: AI 기능 사용 시 대기 시간 안내</li>
<li><strong>실시간 피드백</strong>: 성공/실패 메시지 즉시 표시</li>
<li><strong>점진적 향상</strong>: JavaScript 비활성화 시에도 기본 기능 동작</li>
</ul>
<h3 id="3-접근성-고려">3. 접근성 고려</h3>
<ul>
<li><strong>시맨틱 HTML</strong>: 스크린 리더 지원</li>
<li><strong>키보드 네비게이션</strong>: 마우스 없이도 모든 기능 접근 가능</li>
<li><strong>적절한 색상 대비</strong>: 가독성 향상</li>
</ul>
<hr>
<h2 id="🚧-개발-과정에서-마주한-도전">🚧 개발 과정에서 마주한 도전</h2>
<h3 id="1-대댓글-구조-설계">1. 대댓글 구조 설계</h3>
<p><strong>문제:</strong> 댓글의 계층 구조를 어떻게 표현할까?</p>
<p><strong>해결책:</strong></p>
<pre><code class="language-python">class Comment(models.Model):
    parent = models.ForeignKey(&quot;self&quot;, on_delete=models.CASCADE, 
                              null=True, blank=True)</code></pre>
<ul>
<li><strong>Self-Referencing FK</strong>: 같은 모델을 참조하여 부모-자식 관계 구현</li>
<li><strong>템플릿에서 재귀 처리</strong>: JavaScript로 중첩 댓글 렌더링</li>
</ul>
<h3 id="2-ajax-csrf-토큰-처리">2. Ajax CSRF 토큰 처리</h3>
<p><strong>문제:</strong> Ajax 요청에서 CSRF 토큰 에러 발생</p>
<p><strong>해결책:</strong></p>
<pre><code class="language-javascript">function getCookie(name) {
    // 쿠키에서 CSRF 토큰 추출
    let cookieValue = null;
    if (document.cookie &amp;&amp; document.cookie !== &#39;&#39;) {
        const cookies = document.cookie.split(&#39;;&#39;);
        for (let i = 0; i &lt; cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + &#39;=&#39;)) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}</code></pre>
<hr>
<h2 id="💻-실행-방법">💻 실행 방법</h2>
<h3 id="환경-설정">환경 설정</h3>
<pre><code class="language-bash"># 1. 프로젝트 클론
git clone &lt;repository-url&gt;
cd smartblog

# 2. 가상환경 생성 및 활성화
python -m venv venv
Windows: venv\Scripts\activate # MacOS : source venv/bin/activate

# 3. 패키지 설치
pip install django python-environ openai

# 4. 환경변수 설정 (.env 파일 생성)
DEBUG=True
OPENAI_API_KEY=your_openai_api_key_here
DATABASE_URL=sqlite:///db.sqlite3
ALLOWED_HOSTS=127.0.0.1,localhost</code></pre>
<h3 id="실행">실행</h3>
<pre><code class="language-bash"># 1. 데이터베이스 마이그레이션
python manage.py makemigrations
python manage.py migrate

# 2. 슈퍼유저 생성
python manage.py createsuperuser

# 3. 개발 서버 실행
python manage.py runserver</code></pre>
<h3 id="접속">접속</h3>
<ul>
<li><strong>메인 페이지</strong>: <a href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a></li>
<li><strong>블로그</strong>: <a href="http://127.0.0.1:8000/blog/">http://127.0.0.1:8000/blog/</a></li>
<li><strong>관리자</strong>: <a href="http://127.0.0.1:8000/admin/">http://127.0.0.1:8000/admin/</a></li>
</ul>
<hr>
<h2 id="🎓-마무리">🎓 마무리</h2>
<p>이번 프로젝트를 통해 <strong>Django 프레임워크의 강력함</strong>을 실감했습니다. 특히 ORM의 편리함과 MTV 패턴의 명확한 구조가 개발 효율성을 크게 높여주었습니다.</p>
<p><strong>OpenAI API</strong>를 활용하여 실제로 사용자에게 도움이 되는 기능을 구현할 수 있어서 뿌듯했습니다. </p>
<p>앞으로도 <strong>사용자 중심의 서비스</strong>를 만들기 위해 지속적으로 학습하고 개선해나가겠습니다!</p>
<hr>
<h2 id="🔗-참고-자료">🔗 참고 자료</h2>
<ul>
<li><a href="https://docs.djangoproject.com/">Django 공식 문서</a></li>
<li><a href="https://platform.openai.com/docs">OpenAI API 문서</a></li>
<li><a href="https://getbootstrap.com/docs/5.3/">Bootstrap 5 문서</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[📱 Django로 실시간 채팅 애플리케이션 만들기]]></title>
            <link>https://velog.io/@hyeoni_/Django%EB%A1%9C-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B1%84%ED%8C%85-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hyeoni_/Django%EB%A1%9C-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B1%84%ED%8C%85-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 26 Jun 2025 08:39:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Django를 활용해 간단한 채팅 기능을 구현하는 단계별 가이드입니다. URL 라우팅부터 HTMX를 활용한 비동기 처리까지 차근차근 알아보겠습니다.</p>
</blockquote>
<h2 id="🎯-목표">🎯 목표</h2>
<ul>
<li>Django의 URL 패턴과 View 구조 이해</li>
<li>Template 시스템을 활용한 UI 구성</li>
<li>HTTP 메서드(GET, POST)의 차이점 학습</li>
<li>HTMX를 이용한 비동기 통신 구현</li>
</ul>
<hr>
<h2 id="📁-프로젝트-구조-설정">📁 프로젝트 구조 설정</h2>
<h3 id="1-urls-패턴-구성">1. URLs 패턴 구성</h3>
<p>먼저 채팅 앱의 URL 구조를 설정합니다.</p>
<p><strong>📄 chat/urls.py</strong></p>
<pre><code class="language-python">from django.urls import path
from . import views

urlpatterns = [
    path(&quot;&quot;, views.index),
]</code></pre>
<p><strong>📄 config/urls.py</strong></p>
<pre><code class="language-python">from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&#39;chat/&#39;, include(&#39;chat.urls&#39;)),
]</code></pre>
<blockquote>
<p>💡 <strong>포인트</strong>: <code>include(&#39;chat.urls&#39;)</code>를 사용하면 chat 앱의 모든 URL에 <code>chat/</code> 접두사가 자동으로 붙습니다.</p>
</blockquote>
<hr>
<h2 id="🎨-템플릿-시스템-구축">🎨 템플릿 시스템 구축</h2>
<h3 id="2-기본-html-템플릿-생성">2. 기본 HTML 템플릿 생성</h3>
<p>Django의 템플릿 시스템을 활용해 동적 웹페이지를 만듭니다.</p>
<p><strong>📄 chat/templates/chat/index.html</strong></p>
<ul>
<li>HTML 기본 구조 생성</li>
<li>Pico CSS CDN 링크 추가</li>
<li>HTMX 스크립트 포함</li>
</ul>
<h3 id="3-view에서-템플릿-렌더링">3. View에서 템플릿 렌더링</h3>
<p><strong>📄 chat/views.py</strong></p>
<pre><code class="language-python">def index(request):
    return render(request, &quot;chat/index.html&quot;)</code></pre>
<blockquote>
<p>💡 <strong>Django 템플릿 시스템</strong>: <code>render()</code> 함수는 <code>앱명/templates/</code> 디렉토리에서 자동으로 템플릿 파일을 찾아줍니다.</p>
</blockquote>
<hr>
<h2 id="🔄-http-메서드-이해하기">🔄 HTTP 메서드 이해하기</h2>
<h3 id="http-메서드의-특징">HTTP 메서드의 특징</h3>
<table>
<thead>
<tr>
<th>메서드</th>
<th>용도</th>
<th>특징</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>GET</strong></td>
<td>데이터 조회</td>
<td>안전함, 데이터 변경 없음</td>
<td>검색, 페이지 로드</td>
</tr>
<tr>
<td><strong>POST</strong></td>
<td>데이터 생성/수정</td>
<td>데이터 변경 가능</td>
<td>폼 제출, 채팅 메시지</td>
</tr>
<tr>
<td><strong>PUT/PATCH</strong></td>
<td>데이터 수정</td>
<td>특정 리소스 업데이트</td>
<td>프로필 수정</td>
</tr>
<tr>
<td><strong>DELETE</strong></td>
<td>데이터 삭제</td>
<td>리소스 제거</td>
<td>계정 삭제</td>
</tr>
</tbody></table>
<blockquote>
<p>⚠️ <strong>주의</strong>: HTML <code>&lt;form&gt;</code> 태그는 GET과 POST만 지원합니다. PUT, DELETE 등은 JavaScript API를 통해 사용 가능합니다.</p>
</blockquote>
<hr>
<h2 id="💬-채팅-메시지-처리-구현">💬 채팅 메시지 처리 구현</h2>
<h3 id="4-메시지-처리-view-추가">4. 메시지 처리 View 추가</h3>
<p><strong>📄 chat/views.py</strong></p>
<pre><code class="language-python">def chat_message_new(request: HttpRequest) -&gt; HttpResponse:
    question = request.POST.get(&quot;question&quot;, &quot;&quot;)
    if question:
        answer = f&quot;🤖 당신의 질문: {question}&quot;
    else:
        answer = &quot;❓ 질문이 입력되지 않았습니다.&quot;

    return HttpResponse(answer)</code></pre>
<h3 id="5-url-패턴-연결">5. URL 패턴 연결</h3>
<p><strong>📄 chat/urls.py</strong></p>
<pre><code class="language-python">urlpatterns = [
    path(&quot;&quot;, views.index),
    path(&quot;messages/new/&quot;, views.chat_message_new),  # 새 URL 패턴 추가
]</code></pre>
<hr>
<h2 id="🌐-query-parameters-vs-post-data">🌐 Query Parameters vs POST Data</h2>
<h3 id="query-parameters-이해하기">Query Parameters 이해하기</h3>
<p>Query Parameters는 URL 뒤에 <code>?key=value&amp;key2=value2</code> 형태로 붙는 데이터입니다.</p>
<pre><code>예시: https://example.com/search?q=django&amp;category=tutorial&amp;page=1</code></pre><p><strong>Django에서의 접근 방법:</strong></p>
<pre><code class="language-python"># GET 요청
question = request.GET.get(&quot;question&quot;, &quot;&quot;)

# POST 요청  
question = request.POST.get(&quot;question&quot;, &quot;&quot;)</code></pre>
<hr>
<h2 id="🔒-csrf-보안-이해하기">🔒 CSRF 보안 이해하기</h2>
<h3 id="csrf-토큰이란">CSRF 토큰이란?</h3>
<p><strong>CSRF (Cross-Site Request Forgery)</strong>: 사용자가 의도하지 않은 요청을 공격자가 대신 보내는 것을 방지하는 보안 메커니즘입니다.</p>
<pre><code class="language-html">&lt;form method=&quot;POST&quot;&gt;
  {% csrf_token %}  &lt;!-- Django가 자동으로 보안 토큰 생성 --&gt;
  &lt;!-- 폼 내용 --&gt;
&lt;/form&gt;</code></pre>
<blockquote>
<p>🛡️ <strong>보안 팁</strong>: POST, PUT, DELETE 등 데이터를 변경하는 모든 요청에는 CSRF 토큰이 필요합니다.</p>
</blockquote>
<hr>
<h2 id="⚡-htmx로-비동기-처리하기">⚡ HTMX로 비동기 처리하기</h2>
<h3 id="기존-javascript-vs-htmx">기존 JavaScript vs HTMX</h3>
<p><strong>기존 방식의 문제점:</strong></p>
<ul>
<li>복잡한 이벤트 리스너 설정</li>
<li>fetch API와 Promise 처리</li>
<li>DOM 조작 코드 필요</li>
<li>에러 핸들링 복잡</li>
</ul>
<p><strong>HTMX 방식의 장점:</strong></p>
<pre><code class="language-html">&lt;form hx-post=&quot;/chat/messages/new/&quot; hx-target=&quot;#chat-response&quot;&gt;
  {% csrf_token %}
  &lt;input type=&quot;text&quot; name=&quot;question&quot; /&gt;
  &lt;input type=&quot;submit&quot; value=&quot;전송&quot; /&gt;
&lt;/form&gt;
&lt;div id=&quot;chat-response&quot;&gt;&lt;/div&gt;</code></pre>
<h3 id="htmx-속성-설명">HTMX 속성 설명</h3>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><code>hx-post</code></td>
<td>POST 요청을 보낼 URL</td>
<td><code>hx-post=&quot;/api/submit&quot;</code></td>
</tr>
<tr>
<td><code>hx-target</code></td>
<td>응답을 표시할 요소 선택자</td>
<td><code>hx-target=&quot;#result&quot;</code></td>
</tr>
<tr>
<td><code>hx-get</code></td>
<td>GET 요청을 보낼 URL</td>
<td><code>hx-get=&quot;/api/data&quot;</code></td>
</tr>
<tr>
<td><code>hx-trigger</code></td>
<td>이벤트 트리거 설정</td>
<td><code>hx-trigger=&quot;click&quot;</code></td>
</tr>
</tbody></table>
<hr>
<h2 id="🎨-pico-css로-스타일링">🎨 Pico CSS로 스타일링</h2>
<h3 id="pico-css-특징">Pico CSS 특징</h3>
<ul>
<li><strong>경량화</strong>: 최소한의 CSS로 깔끔한 디자인</li>
<li><strong>반응형</strong>: 모바일 친화적</li>
<li><strong>빠른 적용</strong>: CDN으로 즉시 사용 가능</li>
</ul>
<h3 id="주요-특징">주요 특징</h3>
<ul>
<li><code>.container</code> 클래스로 중앙 정렬</li>
<li>기본 HTML 태그에 자동 스타일 적용</li>
<li>폼 요소들의 자동 스타일링</li>
</ul>
<hr>
<h2 id="🚀-개발-단계별-실행-결과">🚀 개발 단계별 실행 결과</h2>
<h3 id="1-초기-설정-완료">1. 초기 설정 완료</h3>
<ul>
<li><code>http://127.0.0.1:8000/chat</code> 접속</li>
<li>&quot;hello django in html&quot; 메시지 확인</li>
</ul>
<h3 id="2-폼-추가-후">2. 폼 추가 후</h3>
<ul>
<li>질문 입력창과 전송 버튼 표시</li>
<li>Pico CSS 적용으로 깔끔한 디자인</li>
</ul>
<h3 id="3-메시지-처리-구현-후">3. 메시지 처리 구현 후</h3>
<ul>
<li>질문 입력 후 전송 시 응답 메시지 표시</li>
<li>HTMX로 페이지 새로고침 없이 동작</li>
</ul>
<h3 id="4-최종-완성">4. 최종 완성</h3>
<ul>
<li>실시간 채팅 인터페이스 완성</li>
<li>비동기 메시지 처리 구현</li>
</ul>
<hr>
<h2 id="🔧-문제-해결-과정">🔧 문제 해결 과정</h2>
<h3 id="주요-이슈와-해결책">주요 이슈와 해결책</h3>
<ol>
<li><p><strong>&quot;질문 없음&quot; 오류</strong></p>
<ul>
<li>원인: GET 요청으로 POST 데이터 접근</li>
<li>해결: <code>request.POST.get()</code> 사용</li>
</ul>
</li>
<li><p><strong>JavaScript 복잡성</strong></p>
<ul>
<li>원인: 복잡한 fetch API와 이벤트 처리</li>
<li>해결: HTMX로 간단하게 대체</li>
</ul>
</li>
<li><p><strong>CSRF 토큰 누락</strong></p>
<ul>
<li>원인: POST 요청에 보안 토큰 없음</li>
<li>해결: <code>{% csrf_token %}</code> 추가</li>
</ul>
</li>
</ol>
<hr>
<h2 id="📚-학습-포인트">📚 학습 포인트</h2>
<h3 id="django-핵심-개념">Django 핵심 개념</h3>
<ol>
<li><strong>URL 라우팅</strong>: <code>urls.py</code>에서 URL 패턴 정의</li>
<li><strong>View 함수</strong>: 요청을 받아 응답을 반환</li>
<li><strong>Template 시스템</strong>: HTML과 Django 태그 조합</li>
<li><strong>HTTP 메서드</strong>: GET과 POST의 적절한 사용</li>
</ol>
<h3 id="웹-개발-베스트-프랙티스">웹 개발 베스트 프랙티스</h3>
<ol>
<li><strong>보안</strong>: CSRF 토큰으로 보안 강화</li>
<li><strong>사용자 경험</strong>: HTMX로 부드러운 인터렙션</li>
<li><strong>디자인</strong>: Pico CSS로 빠른 스타일링</li>
<li><strong>코드 품질</strong>: 간결하고 읽기 쉬운 코드 작성</li>
</ol>
<hr>
<h2 id="🚀-다음-단계">🚀 다음 단계</h2>
<ol>
<li><strong>데이터베이스 연동</strong>: 채팅 메시지 저장</li>
<li><strong>실시간 기능</strong>: WebSocket 또는 Server-Sent Events</li>
<li><strong>사용자 인증</strong>: 로그인/로그아웃 시스템</li>
<li><strong>API 구축</strong>: REST API로 확장</li>
</ol>
<blockquote>
<p>💪 <strong>도전 과제</strong>: 이 기본 구조를 바탕으로 더 복잡한 채팅 기능을 구현해보세요!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🌳 Git Branch 완벽 가이드: 협업의 필수 도구 마스터하기]]></title>
            <link>https://velog.io/@hyeoni_/Git-Branch</link>
            <guid>https://velog.io/@hyeoni_/Git-Branch</guid>
            <pubDate>Sun, 22 Jun 2025 08:21:19 GMT</pubDate>
            <description><![CDATA[<p>혼자서 코딩할 때는 몰랐지만, 팀 프로젝트를 시작하면서 가장 먼저 부딪히는 벽이 바로 <strong>Git Branch</strong>입니다. &quot;왜 이렇게 복잡하지?&quot; 하며 머리를 감쌌던 경험, 다들 있으시죠? </p>
<p>오늘은 Git Branch의 핵심 개념부터 실전 활용법까지, 한 번에 정리해드릴게요! 💪</p>
<hr>
<h2 id="🤔-branch가-왜-필요할까">🤔 Branch가 왜 필요할까?</h2>
<p>상상해보세요. 친구와 함께 레고 집를 만드는데, 한 명은 1층을, 다른 한 명은 2층을 동시에 만들어야 한다면? 각자 따로 만든 후 나중에 합치는 게 훨씬 효율적이겠죠!</p>
<p><strong>Branch는 바로 이런 &#39;독립적인 작업 공간&#39;을 만들어주는 도구입니다.</strong></p>
<pre><code>main 브랜치 ────○────○────○
                     \
feature 브랜치        ○────○</code></pre><h3 id="✨-branch의-핵심-개념">✨ Branch의 핵심 개념</h3>
<table>
<thead>
<tr>
<th>용어</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>브랜치</strong></td>
<td>독립적인 작업 공간 (코드의 분기점)</td>
</tr>
<tr>
<td><strong>기본 브랜치</strong></td>
<td>master(Git) 또는 main(GitHub)</td>
</tr>
<tr>
<td><strong>HEAD</strong></td>
<td>현재 작업 중인 브랜치를 가리키는 포인터</td>
</tr>
</tbody></table>
<hr>
<h2 id="🛠️-branch-기본-조작법">🛠️ Branch 기본 조작법</h2>
<h3 id="1️⃣-브랜치-현황-파악하기">1️⃣ 브랜치 현황 파악하기</h3>
<pre><code class="language-bash"># 현재 브랜치 목록 확인
git branch

# 결과 예시:
# * main      ← 현재 브랜치 (별표로 표시)
#   feature
#   hotfix</code></pre>
<h3 id="2️⃣-새로운-브랜치-만들기">2️⃣ 새로운 브랜치 만들기</h3>
<pre><code class="language-bash"># 브랜치 생성
git branch feature-login

# main에서 파생되는 브랜치 명시적으로 생성
git branch feature-login main</code></pre>
<h3 id="3️⃣-브랜치-삭제하기">3️⃣ 브랜치 삭제하기</h3>
<pre><code class="language-bash"># 브랜치 삭제 (-D는 강제 삭제)
git branch -D feature-login</code></pre>
<blockquote>
<p>💡 <strong>꿀팁</strong>: 브랜치 이름은 <code>feature-로그인</code>, <code>fix-버그수정</code>처럼 작업 내용을 명확히 표현하세요!</p>
</blockquote>
<hr>
<h2 id="🚀-브랜치-이동-checkout-vs-switch">🚀 브랜치 이동: checkout vs switch</h2>
<p>Git 2.23 버전부터 <code>checkout</code>을 대체할 새로운 명령어들이 등장했습니다!</p>
<h3 id="기존-방식-checkout">기존 방식 (checkout)</h3>
<pre><code class="language-bash"># 브랜치 변경
git checkout feature-login

# 브랜치 생성 + 변경
git checkout -b feature-signup</code></pre>
<h3 id="🆕-새로운-방식-switch---추천">🆕 새로운 방식 (switch) - 추천!</h3>
<pre><code class="language-bash"># 브랜치 변경
git switch feature-login

# 브랜치 생성 + 변경
git switch -c feature-signup</code></pre>
<p><strong>왜 switch를 쓸까요?</strong></p>
<ul>
<li><code>checkout</code>은 기능이 너무 많아서 헷갈림</li>
<li><code>switch</code>는 브랜치 변경에만 집중</li>
<li>더 직관적이고 안전함</li>
</ul>
<hr>
<h2 id="🔧-파일-복원의-새로운-강자-restore">🔧 파일 복원의 새로운 강자: restore</h2>
<p>코딩하다가 &quot;아, 이거 원래대로 돌리고 싶다!&quot; 할 때 사용하는 명령어입니다.</p>
<h3 id="파일-변경사항-취소">파일 변경사항 취소</h3>
<pre><code class="language-bash"># 🆕 새로운 방식 (추천)
git restore README.md

# 기존 방식
git checkout -- README.md</code></pre>
<h3 id="스테이지에서-파일-빼기">스테이지에서 파일 빼기</h3>
<pre><code class="language-bash"># 🆕 새로운 방식 (추천)
git restore --staged README.md

# 기존 방식
git reset HEAD README.md</code></pre>
<blockquote>
<p>⚠️ <strong>주의</strong>: restore로 지운 내용은 커밋하지 않았다면 복구 불가능합니다!</p>
</blockquote>
<hr>
<h2 id="🤝-브랜치-합치기-merge의-마법">🤝 브랜치 합치기: merge의 마법</h2>
<p>여러 브랜치에서 작업한 내용을 하나로 합치는 과정입니다.</p>
<pre><code class="language-bash"># 1. main 브랜치로 이동
git checkout main

# 2. feature 브랜치 내용을 main에 병합
git merge feature-login

# 3. 원격 저장소에 반영
git push origin main</code></pre>
<h3 id="병합-후-브랜치-정리">병합 후 브랜치 정리</h3>
<pre><code class="language-bash"># 병합이 완료된 브랜치 삭제
git branch -d feature-login

# 원격 브랜치도 삭제
git push origin --delete feature-login</code></pre>
<hr>
<h2 id="💥-충돌conflict-해결하기">💥 충돌(Conflict) 해결하기</h2>
<p>가장 무서워하는 상황이지만, 사실 그렇게 어렵지 않아요!</p>
<h3 id="충돌이-발생하는-이유">충돌이 발생하는 이유</h3>
<p>두 브랜치가 <strong>같은 파일의 같은 부분</strong>을 다르게 수정했을 때 발생합니다.</p>
<h3 id="충돌-해결-과정">충돌 해결 과정</h3>
<pre><code class="language-bash"># 충돌 발생 시 파일 내용
&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD (현재 브랜치)
hello world
hello binky
=======
hello world         
hello gary
&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature-branch (병합하려는 브랜치)</code></pre>
<p><strong>해결 방법:</strong></p>
<ol>
<li>원하는 코드만 남기고 <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code>, <code>=======</code>, <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code> 삭제</li>
<li>수정 완료 후 저장</li>
<li><code>git add</code> → <code>git commit</code> → <code>git push</code></li>
</ol>
<h3 id="🎨-vscode에서-충돌-해결하기">🎨 VSCode에서 충돌 해결하기</h3>
<p>VSCode는 충돌 해결을 위한 편리한 UI를 제공합니다:</p>
<ul>
<li><strong>Accept Current Change</strong>: 현재 브랜치 내용 선택</li>
<li><strong>Accept Incoming Change</strong>: 병합하려는 브랜치 내용 선택  </li>
<li><strong>Accept Both Changes</strong>: 둘 다 선택</li>
<li><strong>Compare Changes</strong>: 변경사항 비교</li>
</ul>
<hr>
<h2 id="🆘-실수했을-때-브랜치-복구하기">🆘 실수했을 때: 브랜치 복구하기</h2>
<p>&quot;아! 브랜치를 잘못 삭제했다!&quot; 당황하지 마세요. Git은 복구 방법을 제공합니다.</p>
<h3 id="2단계-복구-과정">2단계 복구 과정</h3>
<h4 id="1단계-복구-시점-찾기">1단계: 복구 시점 찾기</h4>
<pre><code class="language-bash"># 모든 참조 내역 확인
git reflog

# 결과 예시:
# a1b2c3d HEAD@{0}: branch: deleted branch feature-login
# e4f5g6h HEAD@{1}: commit: add login function</code></pre>
<h4 id="2단계-브랜치-복구하기">2단계: 브랜치 복구하기</h4>
<pre><code class="language-bash"># 커밋 해시값을 이용해 브랜치 복구
git checkout -b feature-login e4f5g6h</code></pre>
<hr>
<h2 id="🎯-실전-팁--베스트-프랙티스">🎯 실전 팁 &amp; 베스트 프랙티스</h2>
<h3 id="💡-브랜치-네이밍-컨벤션">💡 브랜치 네이밍 컨벤션</h3>
<pre><code class="language-bash"># 기능 개발
feature/user-authentication
feature/payment-system

# 버그 수정
fix/login-error
fix/memory-leak

# 긴급 수정
hotfix/security-patch</code></pre>
<h3 id="🔍-유용한-명령어들">🔍 유용한 명령어들</h3>
<pre><code class="language-bash"># 모든 브랜치의 커밋 그래프로 보기
git log --all --decorate --oneline --graph

# 원격 브랜치 목록 확인
git branch -r

# 로컬 + 원격 브랜치 모두 보기
git branch -a</code></pre>
<h3 id="⚡-워크플로우-예시">⚡ 워크플로우 예시</h3>
<pre><code class="language-bash"># 1. 새 기능 개발 시작
git switch -c feature/new-dashboard

# 2. 작업 완료 후 커밋
git add .
git commit -m &quot;feat: add new dashboard&quot;

# 3. 원격에 푸시
git push -u origin feature/new-dashboard

# 4. main으로 병합
git switch main
git merge feature/new-dashboard

# 5. 브랜치 정리
git branch -d feature/new-dashboard
git push origin --delete feature/new-dashboard</code></pre>
<hr>
<h2 id="🎉-마무리">🎉 마무리</h2>
<p>Branch를 마스터하면 협업이 훨씬 수월해집니다. 처음엔 복잡해 보이지만, 몇 번 써보면 금세 익숙해져요!</p>
<p><strong>핵심만 기억하세요:</strong></p>
<ul>
<li>🌱 기능별로 브랜치 나누기</li>
<li>🔄 작업 완료 후 merge하기  </li>
<li>🧹 사용 완료된 브랜치 정리하기</li>
<li>💥 충돌 발생 시 차근차근 해결하기</li>
</ul>
<p>오늘부터 Branch를 적극 활용해서 깔끔한 Git 히스토리를 만들어보세요! 🚀</p>
<hr>
<p><em>이 글이 도움되셨다면 동료 개발자들과도 공유해주세요! 💝</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🚀 FastAPI 백엔드 개발 기초 가이드]]></title>
            <link>https://velog.io/@hyeoni_/FastAPIProject</link>
            <guid>https://velog.io/@hyeoni_/FastAPIProject</guid>
            <pubDate>Sat, 14 Jun 2025 08:56:19 GMT</pubDate>
            <description><![CDATA[<p><em><strong>👉 미니 프로젝트로 ChatGPT API를 사용한 &quot;모두의 레시피&quot;를 만들면서 어려웠던 점을 요약해보았습니다.</strong></em></p>
<hr>
<h2 id="📚-목차">📚 목차</h2>
<ol>
<li><a href="#1-%ED%95%84%EC%88%98-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-import-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">필수 라이브러리 Import 이해하기</a></li>
<li><a href="#2-fastapi-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B8%B0%EB%B3%B8-%EC%84%A4%EC%A0%95">FastAPI 애플리케이션 기본 설정</a></li>
<li><a href="#3-http-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-requests-vs-httpx">HTTP 클라이언트 라이브러리 (requests vs httpx)</a></li>
<li><a href="#5-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%80%EC%A6%9D-%EB%B0%8F-%EB%B3%B4%EC%95%88">데이터 검증 및 보안</a></li>
<li><a href="#%EB%A7%88%EB%AC%B4%EB%A6%AC">마무리</a></li>
</ol>
<hr>
<h2 id="1-필수-라이브러리-import-이해하기">1. 필수 라이브러리 Import 이해하기</h2>
<h3 id="📦-모듈별-상세-설명">📦 모듈별 상세 설명</h3>
<h4 id="fastapi-핵심-모듈들"><strong>FastAPI 핵심 모듈들</strong></h4>
<table>
<thead>
<tr>
<th>모듈</th>
<th>역할</th>
<th>실무 활용도</th>
</tr>
</thead>
<tbody><tr>
<td><code>FastAPI</code></td>
<td>메인 애플리케이션 클래스</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><code>Request</code></td>
<td>HTTP 요청 객체 처리</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td><code>Form</code></td>
<td>HTML 폼 데이터 처리</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td><code>HTTPException</code></td>
<td>HTTP 에러 처리</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><code>Cookie</code></td>
<td>쿠키 데이터 처리</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td><code>Depends</code></td>
<td>의존성 주입</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
</tbody></table>
<h4 id="응답-관련-모듈들"><strong>응답 관련 모듈들</strong></h4>
<ul>
<li>🌐 <strong>HTMLResponse</strong>: HTML 형태 응답 (웹페이지 반환)</li>
<li>🔄 <strong>RedirectResponse</strong>: 페이지 리다이렉션 (로그인 후 메인으로 이동)</li>
<li>📊 <strong>JSONResponse</strong>: JSON 형태 응답 (API 데이터 반환)</li>
</ul>
<h4 id="템플릿과-정적-파일"><strong>템플릿과 정적 파일</strong></h4>
<ul>
<li>🎨 <strong>Jinja2Templates</strong>: HTML 템플릿 엔진 (Django 템플릿과 유사)</li>
<li>📁 <strong>StaticFiles</strong>: CSS, JS, 이미지 등 정적 파일 서빙</li>
</ul>
<h4 id="cors-미들웨어"><strong>CORS 미들웨어</strong></h4>
<ul>
<li>🔐 <strong>CORSMiddleware</strong>: 다른 도메인 간 통신 허용 설정</li>
</ul>
<hr>
<h2 id="2-fastapi-애플리케이션-기본-설정">2. FastAPI 애플리케이션 기본 설정</h2>
<h3 id="🛠️-cors-미들웨어-설정">🛠️ CORS 미들웨어 설정</h3>
<pre><code class="language-python"># CORS 미들웨어 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=[&quot;*&quot;],           # 모든 도메인 허용
    allow_credentials=True,        # 인증 정보 포함 요청 허용
    allow_methods=[&quot;*&quot;],          # 모든 HTTP 메서드 허용
    allow_headers=[&quot;*&quot;],          # 모든 헤더 허용
)</code></pre>
<h4 id="🔍-cors란">🔍 CORS란?</h4>
<p><strong>Cross-Origin Resource Sharing</strong>의 줄임말로, 웹 보안 정책입니다.</p>
<table>
<thead>
<tr>
<th>설정</th>
<th>설명</th>
<th>개발환경</th>
<th>운영환경</th>
</tr>
</thead>
<tbody><tr>
<td><code>allow_origins</code></td>
<td>허용할 도메인</td>
<td><code>[&quot;*&quot;]</code></td>
<td><code>[&quot;https://myapp.com&quot;]</code></td>
</tr>
<tr>
<td><code>allow_credentials</code></td>
<td>인증정보 포함 요청</td>
<td><code>True</code></td>
<td><code>True</code></td>
</tr>
<tr>
<td><code>allow_methods</code></td>
<td>허용 HTTP 메서드</td>
<td><code>[&quot;*&quot;]</code></td>
<td><code>[&quot;GET&quot;, &quot;POST&quot;]</code></td>
</tr>
<tr>
<td><code>allow_headers</code></td>
<td>허용 헤더</td>
<td><code>[&quot;*&quot;]</code></td>
<td>필요한 헤더만</td>
</tr>
</tbody></table>
<blockquote>
<p>⚠️ <strong>보안 주의사항</strong>: 실제 배포 시에는 <code>allow_origins</code>를 특정 도메인으로 제한하세요!</p>
</blockquote>
<h3 id="🎯-정적-파일-서빙-설정">🎯 정적 파일 서빙 설정</h3>
<pre><code class="language-python"># 정적 파일 사용 설정
app.mount(&quot;/static&quot;, StaticFiles(directory=&quot;static&quot;), name=&quot;static&quot;)</code></pre>
<h3 id="🎨-html-템플릿-엔진-설정">🎨 HTML 템플릿 엔진 설정</h3>
<pre><code class="language-python"># HTML 템플릿 경로 설정
templates = Jinja2Templates(directory=&quot;templates&quot;)</code></pre>
<h4 id="🔧-템플릿-사용법">🔧 템플릿 사용법</h4>
<pre><code class="language-python">@app.get(&quot;/&quot;)
async def read_root(request: Request):
    return templates.TemplateResponse(&quot;index.html&quot;, {
        &quot;request&quot;: request,           # 필수 파라미터
        &quot;title&quot;: &quot;홈페이지&quot;,          # 템플릿 변수
        &quot;user&quot;: &quot;김개발자&quot;           # 템플릿 변수
    })</code></pre>
<p><strong>templates/index.html</strong>:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;{{ title }}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;안녕하세요, {{ user }}님!&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h2 id="3-http-클라이언트-라이브러리-requests-vs-httpx">3. HTTP 클라이언트 라이브러리 (requests vs httpx)</h2>
<h3 id="🔄-requests-vs-httpx-완벽-비교">🔄 requests vs httpx 완벽 비교</h3>
<table>
<thead>
<tr>
<th>특징</th>
<th>requests</th>
<th>httpx</th>
<th>실무 추천도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>동기 지원</strong></td>
<td>✅ 완벽 지원</td>
<td>✅ 완벽 지원</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>비동기 지원</strong></td>
<td>❌ 미지원</td>
<td>✅ 완벽 지원</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>HTTP/2 지원</strong></td>
<td>❌ 미지원</td>
<td>✅ 완벽 지원</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>현대적인 API</strong></td>
<td>⚠️ 오래된 설계</td>
<td>✅ 최신 설계</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>보통</td>
<td>우수</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>FastAPI 호환성</strong></td>
<td>제한적</td>
<td>완벽</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
</tbody></table>
<h3 id="📝-동기-http-클라이언트-사용법">📝 동기 HTTP 클라이언트 사용법</h3>
<h4 id="requests-사용-예시"><strong>requests 사용 예시</strong></h4>
<pre><code class="language-python">import requests

# 기본 GET 요청
def get_user_info_sync(user_id: int):
    &quot;&quot;&quot;동기 방식으로 사용자 정보 조회&quot;&quot;&quot;
    response = requests.get(f&quot;https://api.example.com/users/{user_id}&quot;)

    if response.status_code == 200:
        return response.json()
    else:
        return {&quot;error&quot;: f&quot;사용자 조회 실패: {response.status_code}&quot;}

# POST 요청 (JSON 데이터)
def create_user_sync(user_data: dict):
    &quot;&quot;&quot;동기 방식으로 사용자 생성&quot;&quot;&quot;
    response = requests.post(
        &quot;https://api.example.com/users&quot;,
        json=user_data,
        headers={&quot;Content-Type&quot;: &quot;application/json&quot;}
    )
    return response.json()</code></pre>
<h4 id="httpx-동기-사용-예시"><strong>httpx 동기 사용 예시</strong></h4>
<pre><code class="language-python">import httpx

# httpx로 동기 요청 (requests와 거의 동일한 API)
def get_user_info_sync_httpx(user_id: int):
    &quot;&quot;&quot;httpx로 동기 방식 사용자 정보 조회&quot;&quot;&quot;
    with httpx.Client() as client:
        response = client.get(f&quot;https://api.example.com/users/{user_id}&quot;)

        if response.status_code == 200:
            return response.json()
        else:
            return {&quot;error&quot;: f&quot;사용자 조회 실패: {response.status_code}&quot;}</code></pre>
<h3 id="⚡-비동기-http-클라이언트-사용법-httpx만-지원">⚡ 비동기 HTTP 클라이언트 사용법 (httpx만 지원)</h3>
<pre><code class="language-python">import httpx
import asyncio

# 비동기 GET 요청
async def get_user_info_async(user_id: int):
    &quot;&quot;&quot;비동기 방식으로 사용자 정보 조회&quot;&quot;&quot;
    async with httpx.AsyncClient() as client:
        response = await client.get(f&quot;https://api.example.com/users/{user_id}&quot;)

        if response.status_code == 200:
            return response.json()
        else:
            return {&quot;error&quot;: f&quot;사용자 조회 실패: {response.status_code}&quot;}

# 비동기 POST 요청
async def create_user_async(user_data: dict):
    &quot;&quot;&quot;비동기 방식으로 사용자 생성&quot;&quot;&quot;
    async with httpx.AsyncClient() as client:
        response = await client.post(
            &quot;https://api.example.com/users&quot;,
            json=user_data,
            headers={&quot;Content-Type&quot;: &quot;application/json&quot;}
        )
        return response.json()

# 여러 요청 동시 처리
async def get_multiple_users(user_ids: list):
    &quot;&quot;&quot;여러 사용자 정보를 동시에 조회&quot;&quot;&quot;
    async with httpx.AsyncClient() as client:
        tasks = [
            client.get(f&quot;https://api.example.com/users/{user_id}&quot;) 
            for user_id in user_ids
        ]

        # 모든 요청을 동시에 실행
        responses = await asyncio.gather(*tasks)

        return [
            response.json() if response.status_code == 200 else None
            for response in responses
        ]</code></pre>
<h3 id="🚀-fastapi에서-http-클라이언트-활용">🚀 FastAPI에서 HTTP 클라이언트 활용</h3>
<h4 id="외부-api-호출하는-엔드포인트-예시"><strong>외부 API 호출하는 엔드포인트 예시</strong></h4>
<pre><code class="language-python">from fastapi import FastAPI
import httpx

app = FastAPI()

# 동기 방식 (간단한 작업용)
@app.get(&quot;/user/{user_id}&quot;)
def get_external_user(user_id: int):
    &quot;&quot;&quot;외부 API에서 사용자 정보 조회 (동기)&quot;&quot;&quot;
    with httpx.Client() as client:
        response = client.get(f&quot;https://jsonplaceholder.typicode.com/users/{user_id}&quot;)

        if response.status_code == 200:
            return {&quot;success&quot;: True, &quot;data&quot;: response.json()}
        else:
            return {&quot;success&quot;: False, &quot;error&quot;: &quot;사용자를 찾을 수 없습니다&quot;}

# 비동기 방식 (권장)
@app.get(&quot;/user-async/{user_id}&quot;)
async def get_external_user_async(user_id: int):
    &quot;&quot;&quot;외부 API에서 사용자 정보 조회 (비동기)&quot;&quot;&quot;
    async with httpx.AsyncClient() as client:
        response = await client.get(f&quot;https://jsonplaceholder.typicode.com/users/{user_id}&quot;)

        if response.status_code == 200:
            return {&quot;success&quot;: True, &quot;data&quot;: response.json()}
        else:
            return {&quot;success&quot;: False, &quot;error&quot;: &quot;사용자를 찾을 수 없습니다&quot;}

# 복잡한 외부 API 연동 예시
@app.get(&quot;/weather/{city}&quot;)
async def get_weather(city: str):
    &quot;&quot;&quot;날씨 API 연동 예시&quot;&quot;&quot;
    async with httpx.AsyncClient() as client:
        # 실제로는 API 키가 필요합니다
        response = await client.get(
            f&quot;https://api.openweathermap.org/data/2.5/weather&quot;,
            params={
                &quot;q&quot;: city,
                &quot;appid&quot;: &quot;YOUR_API_KEY&quot;,
                &quot;units&quot;: &quot;metric&quot;,
                &quot;lang&quot;: &quot;kr&quot;
            }
        )

        if response.status_code == 200:
            weather_data = response.json()
            return {
                &quot;city&quot;: weather_data[&quot;name&quot;],
                &quot;temperature&quot;: weather_data[&quot;main&quot;][&quot;temp&quot;],
                &quot;description&quot;: weather_data[&quot;weather&quot;][0][&quot;description&quot;]
            }
        else:
            return {&quot;error&quot;: &quot;날씨 정보를 가져올 수 없습니다&quot;}</code></pre>
<h3 id="🎯-실무에서의-선택-기준">🎯 실무에서의 선택 기준</h3>
<h4 id="requests를-사용해야-하는-경우"><strong>requests를 사용해야 하는 경우</strong></h4>
<ul>
<li>✅ 간단한 스크립트나 배치 작업</li>
<li>✅ 동기 처리만 필요한 경우</li>
<li>✅ 기존 레거시 코드와의 호환성이 중요한 경우</li>
</ul>
<h4 id="httpx를-사용해야-하는-경우-권장"><strong>httpx를 사용해야 하는 경우</strong> (권장)</h4>
<ul>
<li>✅ FastAPI 애플리케이션 개발</li>
<li>✅ 비동기 처리가 필요한 경우</li>
<li>✅ HTTP/2 지원이 필요한 경우</li>
<li>✅ 최신 기능과 성능이 중요한 경우</li>
</ul>
<hr>
<h2 id="4-데이터-검증-및-보안">4. 데이터 검증 및 보안</h2>
<h3 id="🧹-텍스트-정리-함수">🧹 텍스트 정리 함수</h3>
<pre><code class="language-python">def clean_text(text: str) -&gt; str:
    &quot;&quot;&quot;텍스트에서 HTML 태그 제거 및 특수문자 처리&quot;&quot;&quot;
    if not text:
        return &quot;&quot;

    # 1️⃣ HTML 엔티티 디코딩
    text = html.unescape(text)

    # 2️⃣ HTML 태그 제거
    text = re.sub(r&#39;&lt;[^&gt;]+&gt;&#39;, &#39;&#39;, text)

    # 3️⃣ 연속된 공백을 하나로 변경
    text = re.sub(r&#39;\s+&#39;, &#39; &#39;, text)

    # 4️⃣ 앞뒤 공백 제거
    text = text.strip()

    return text</code></pre>
<h4 id="🛡️-xss-공격-방지-과정">🛡️ XSS 공격 방지 과정</h4>
<table>
<thead>
<tr>
<th>단계</th>
<th>처리 내용</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>HTML 엔티티 디코딩</strong></td>
<td><code>&amp;lt;</code> → <code>&lt;</code></td>
<td><code>&amp;lt;script&amp;gt;</code> → <code>&lt;script&gt;</code></td>
</tr>
<tr>
<td><strong>HTML 태그 제거</strong></td>
<td>모든 태그 삭제</td>
<td><code>&lt;script&gt;alert()&lt;/script&gt;</code> → <code>alert()</code></td>
</tr>
<tr>
<td><strong>공백 정규화</strong></td>
<td>연속 공백 통합</td>
<td><code>안녕    하세요</code> → <code>안녕 하세요</code></td>
</tr>
<tr>
<td><strong>공백 제거</strong></td>
<td>앞뒤 불필요한 공백 제거</td>
<td><code>텍스트</code> → <code>텍스트</code></td>
</tr>
</tbody></table>
<h3 id="✅-입력-검증-함수">✅ 입력 검증 함수</h3>
<pre><code class="language-python">def validate_recipe_input(title: str, content: str) -&gt; None:
    &quot;&quot;&quot;레시피 입력 데이터 검증&quot;&quot;&quot;

    # 🧹 텍스트 정리
    title = clean_text(title) if title else &quot;&quot;
    content = clean_text(content) if content else &quot;&quot;

    # 📝 필수값 검증
    if not title:
        raise HTTPException(
            status_code=400,
            detail=&quot;❌ 레시피 제목을 입력해주세요&quot;
        )

    if not content:
        raise HTTPException(
            status_code=400,
            detail=&quot;❌ 레시피 내용을 입력해주세요&quot;
        )

    # 📏 길이 제한 검증
    if len(title) &gt; 200:
        raise HTTPException(
            status_code=400,
            detail=&quot;❌ 제목은 200자 이내로 입력해주세요&quot;
        )

    if len(content) &gt; 10000:
        raise HTTPException(
            status_code=400,
            detail=&quot;❌ 내용은 10,000자 이내로 입력해주세요&quot;
        )</code></pre>
<h4 id="📊-검증-규칙-요약">📊 검증 규칙 요약</h4>
<table>
<thead>
<tr>
<th>항목</th>
<th>규칙</th>
<th>에러 코드</th>
<th>메시지</th>
</tr>
</thead>
<tbody><tr>
<td><strong>제목 필수</strong></td>
<td>빈 값 불허</td>
<td>400</td>
<td>&quot;레시피 제목을 입력해주세요&quot;</td>
</tr>
<tr>
<td><strong>내용 필수</strong></td>
<td>빈 값 불허</td>
<td>400</td>
<td>&quot;레시피 내용을 입력해주세요&quot;</td>
</tr>
<tr>
<td><strong>제목 길이</strong></td>
<td>200자 이내</td>
<td>400</td>
<td>&quot;제목은 200자 이내로 입력해주세요&quot;</td>
</tr>
<tr>
<td><strong>내용 길이</strong></td>
<td>10,000자 이내</td>
<td>400</td>
<td>&quot;내용은 10,000자 이내로 입력해주세요&quot;</td>
</tr>
</tbody></table>
<hr>
<h2 id="🎯-마무리">🎯 마무리</h2>
<h3 id="💡-핵심-개념-요약">💡 핵심 개념 요약</h3>
<table>
<thead>
<tr>
<th>개념</th>
<th>설명</th>
<th>실무 중요도</th>
</tr>
</thead>
<tbody><tr>
<td><strong>체계적인 Import</strong></td>
<td>모듈을 용도별로 정리하여 가독성 향상</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td><strong>CORS 설정</strong></td>
<td>프론트엔드-백엔드 간 안전한 통신</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>정적 파일 서빙</strong></td>
<td>CSS, JS 등 정적 자원 효율적 제공</td>
<td>⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>템플릿 엔진</strong></td>
<td>동적 HTML 생성으로 사용자 경험 향상</td>
<td>⭐⭐⭐</td>
</tr>
<tr>
<td><strong>HTTP 클라이언트</strong></td>
<td>외부 API 연동 (httpx 권장)</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>의존성 주입</strong></td>
<td>코드 재사용성과 테스트 용이성 확보</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
<tr>
<td><strong>데이터 검증</strong></td>
<td>XSS 방지와 데이터 무결성 보장</td>
<td>⭐⭐⭐⭐⭐</td>
</tr>
</tbody></table>
<h3 id="🛠️-실무-프로젝트-아이디어">🛠️ 실무 프로젝트 아이디어</h3>
<ol>
<li><p><strong>날씨 대시보드</strong></p>
<ul>
<li>OpenWeatherMap API 연동</li>
<li>여러 도시 날씨 동시 조회</li>
<li>실시간 업데이트 기능</li>
</ul>
</li>
<li><p><strong>소셜 미디어 집계기</strong></p>
<ul>
<li>여러 SNS API 동시 호출</li>
<li>데이터 통합 및 분석</li>
<li>캐싱 전략 구현</li>
</ul>
</li>
<li><p><strong>마이크로서비스 게이트웨이</strong></p>
<ul>
<li>여러 내부 서비스 API 라우팅</li>
<li>로드 밸런싱 및 헬스체크</li>
<li>API 응답 시간 모니터링</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[🧠 Python 데이터 구조 알아보기: 리스트, 튜플, 딕셔너리, 집합]]></title>
            <link>https://velog.io/@hyeoni_/PythonDataStructure</link>
            <guid>https://velog.io/@hyeoni_/PythonDataStructure</guid>
            <pubDate>Thu, 29 May 2025 09:35:35 GMT</pubDate>
            <description><![CDATA[<p>이 페이지에서는 파이썬의 기본 데이터 구조인 리스트, 튜플, 딕셔너리, 집합에 대해 다루고 있습니다. 각각의 특징과 사용법을 살펴보며, 효율적인 코딩을 위한 팁도 함께 공유하겠습니다.😊</p>
<h3 id="📌-리스트list-순서가-있는-가변-시퀀스">📌 리스트(List): 순서가 있는 가변 시퀀스</h3>
<p>리스트는 파이썬에서 가장 많이 사용되는 데이터 구조 중 하나입니다. 순서가 있으며, 요소를 추가하거나 제거할 수 있는 가변 시퀀스입니다.</p>
<pre><code class="language-python">fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;cherry&#39;]
fruits.append(&#39;date&#39;)  # 요소 추가
fruits.remove(&#39;banana&#39;)  # 요소 제거</code></pre>
<p>리스트는 다양한 메서드를 제공하여 데이터를 쉽게 조작할 수 있습니다. 예를 들어, append(), remove(), sort(), reverse() 등이 있습니다.</p>
<h3 id="📌-튜플tuple-순서가-있는-불변-시퀀스">📌 튜플(Tuple): 순서가 있는 불변 시퀀스</h3>
<p>튜플은 리스트와 비슷하지만, 한 번 생성하면 변경할 수 없는 불변 시퀀스입니다. 데이터의 무결성을 유지하고자 할 때 유용하게 사용됩니다.</p>
<pre><code class="language-python">coordinates = (10, 20)</code></pre>
<p>튜플은 리스트보다 메모리 사용이 적고, 해시 가능하다는 장점이 있어 딕셔너리의 키로 사용할 수 있습니다.</p>
<h3 id="📌-딕셔너리dictionary-키-값-쌍의-집합">📌 딕셔너리(Dictionary): 키-값 쌍의 집합</h3>
<p>딕셔너리는 키와 값의 쌍으로 데이터를 저장하는 구조입니다. 키를 통해 값을 빠르게 조회할 수 있어 매우 효율적입니다.</p>
<pre><code class="language-python">person = {&#39;name&#39;: &#39;Alice&#39;, &#39;age&#39;: 30}
print(person[&#39;name&#39;])  # &#39;Alice&#39;</code></pre>
<p>딕셔너리는 get(), keys(), values(), items() 등의 메서드를 제공하여 데이터를 쉽게 다룰 수 있습니다.</p>
<h3 id="📌-집합set-중복-없는-요소들의-모음">📌 집합(Set): 중복 없는 요소들의 모음</h3>
<p>집합은 중복을 허용하지 않는 데이터 구조로, 수학의 집합과 유사한 개념입니다. 요소 간의 중복을 제거하거나, 교집합, 합집합, 차집합 등의 연산을 수행할 때 유용합니다.</p>
<pre><code class="language-python">numbers = {1, 2, 3, 4}
numbers.add(5)
numbers.remove(2)</code></pre>
<p>집합은 add(), remove(), union(), intersection(), difference() 등의 메서드를 제공합니다.</p>
<p>이렇게 파이썬의 기본 데이터 구조들을 살펴보았습니다. 각각의 구조는 특정 상황에 따라 적절하게 사용되어야 하며, 이를 잘 이해하고 활용하는 것이 효율적인 코딩의 시작입니다. 읽어주셔서 감사합니다!🤗</p>
]]></description>
        </item>
    </channel>
</rss>