๋ฆฌ์ž

๐Ÿถ ์• ๊ฒฌ ์ปค๋ฎค๋‹ˆํ‹ฐ ํ™ˆํŽ˜์ด์ง€ - ๋ฐ˜๋ ค๊ฒฌ ์ผ์ง€ & ๋…ธํ•˜์šฐ ๊ณต์œ  ํ”Œ๋žซํผ (Laravel 13.x + Blade) - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ

https://sir.kr/jobs/requests/20625
์œ„ ์˜๋ขฐ ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ๋ฅผ ๋งŒ๋“ค์–ด ๋ดค์Šต๋‹ˆ๋‹คย 

ย 
## ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

๋ฐ˜๋ ค๊ฒฌ ๋ณดํ˜ธ์ž๋“ค์„ ์œ„ํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. ๊ฐœ์ธ ์• ๊ฒฌ์ผ์ง€๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ์„ ํƒ์ ์œผ๋กœ ๊ณต๊ฐœํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์–‘์œก ๋…ธํ•˜์šฐ๋ฅผ ๊ณต์œ ํ•˜๊ณ  ๋Œ“๊ธ€๋กœ ์†Œํ†ตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž์˜ ๊ณต์ง€์‚ฌํ•ญ์€ ์›น ํ‘ธ์‹œ๋กœ ํšŒ์›์—๊ฒŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.

## ํ•„์ˆ˜ ๊ธฐ๋Šฅ

- ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ (์ด๋ฉ”์ผ ๊ธฐ๋ฐ˜ ์ธ์ฆ)
- ์• ๊ฒฌ์ผ์ง€ ์ž‘์„ฑ (๋น„๊ณต๊ฐœ ๊ธฐ๋ณธ, ์„ ํƒ์  ๊ณต๊ฐœ)
- ์• ๊ฒฌ์ผ์ง€ ๊ณต๊ฐœ์šฉ ๊ฐค๋Ÿฌ๋ฆฌ (๊ณต๊ฐœ๋œ ์ผ์ง€๋งŒ ๋…ธ์ถœ)
- ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œํŒ (์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ธ€ ์ž‘์„ฑ, ๋Œ“๊ธ€)
- ๊ด€๋ฆฌ์ž ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ (์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ)
- ๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ ๋ฐ ์›น ํ‘ธ์‹œ ์•Œ๋ฆผ ๋ฐœ์†ก
- ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ ์•ฑ ์ „ํ™˜ ๋Œ€๋น„)

## ๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ

- ์• ๊ฒฌ ํ”„๋กœํ•„ ๋“ฑ๋ก (์ด๋ฆ„, ๊ฒฌ์ข…, ๋‚˜์ด, ์‚ฌ์ง„)
- ์• ๊ฒฌ์ผ์ง€ ์‚ฌ์ง„ ๋‹ค์ค‘ ์—…๋กœ๋“œ
- ๊ฒŒ์‹œ๊ธ€/๋Œ“๊ธ€ ์ข‹์•„์š” ๊ธฐ๋Šฅ
- ์ธ๊ธฐ ๊ฒŒ์‹œ๊ธ€ ๋žญํ‚น
- ๋‹คํฌ ๋ชจ๋“œ ์ง€์›

## ๊ธฐ์ˆ  ์Šคํƒ

- Laravel 13.x (๋ฐฑ์—”๋“œ)
- Blade + Alpine.js (ํ”„๋ก ํŠธ์—”๋“œ)
- Tailwind CSS (์Šคํƒ€์ผ๋ง)
- SQLite (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค)
- Laravel Web Push (laravel-notification-channels/webpush)

## ์ œ์•ฝ ์กฐ๊ฑด

- Laravel 13.x + Blade ํ…œํ”Œ๋ฆฟ ์—”์ง„ ๊ธฐ๋ฐ˜ MPA ๊ตฌ์กฐ
- Laravel Breeze Blade starter kit์œผ๋กœ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
- SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (๊ฐ„ํŽธํ•œ ๋กœ์ปฌ ๊ฐœ๋ฐœ)
- Tailwind CSS๋กœ ๋ชจ๋“  ์Šคํƒ€์ผ๋ง ์ฒ˜๋ฆฌ
- Alpine.js๋กœ ํด๋ผ์ด์–ธํŠธ ์ธก ์ธํ„ฐ๋ž™์…˜ ์ฒ˜๋ฆฌ (๋ชจ๋‹ฌ, ํ† ๊ธ€ ๋“ฑ)
- Laravel Eloquent ORM๊ณผ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ™œ์šฉ
- ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋Š” Laravel Storage (public ๋””์Šคํฌ) ์‚ฌ์šฉ
- ํผ ๊ฒ€์ฆ์€ Laravel Form Request Validation ์‚ฌ์šฉ
- ์›น ํ‘ธ์‹œ๋Š” laravel-notification-channels/webpush ํŒจํ‚ค์ง€ ์‚ฌ์šฉ
- ํ–ฅํ›„ ๋ชจ๋ฐ”์ผ ์•ฑ ์ „ํ™˜์„ ๊ณ ๋ คํ•˜์—ฌ ์ปจํŠธ๋กค๋Ÿฌ ๋กœ์ง๊ณผ ๋ทฐ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ , ํ•„์š” ์‹œ API ๋ผ์šฐํŠธ ์ถ”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ์„ค๊ณ„

---

## ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ - Step๋ณ„ ๊ฐ€์ด๋“œ

---

### Step 1: Laravel 13.x + Breeze Blade ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

**ํ”„๋กฌํ”„ํŠธ:**

mkdir dog-community && cd dog-community

์ด ํด๋”์—์„œ Laravel 13.x + Blade ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ช…๋ น์œผ๋กœ Laravel Breeze Blade starter kit ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:

composer create-project laravel/laravel . composer require laravel/breeze --dev php artisan breeze:install blade php artisan migrate npm install npm run build

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ฅผ ํ™•์ธํ•˜๊ณ , .env ํŒŒ์ผ์—์„œ DB_CONNECTION=sqlite๋กœ ์„ค์ •ํ•œ ํ›„ database/database.sqlite ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”.

Alpine.js๋Š” Breeze Blade์— ๊ธฐ๋ณธ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๋ณ„๋„ ์„ค์น˜๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์ธ์ฆ ๊ธฐ๋Šฅ(๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…)์ด ํฌํ•จ๋œ Breeze ์Šคํƒ€ํ„ฐ ํ‚ท์ด ์„ค์น˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , npm run dev๋กœ ํ”„๋ก ํŠธ์—”๋“œ ๋นŒ๋“œ๊ฐ€ ์ •์ƒ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

APP_NAME์„ "๋ฉ๋ฉ์ปค๋ฎค๋‹ˆํ‹ฐ"๋กœ ์„ค์ •ํ•˜์„ธ์š”.


**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ:** "Laravel 13.x + Breeze Blade ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์„ค์ •๋˜๋ฉฐ, ๊ธฐ๋ณธ ์ธ์ฆ ํŽ˜์ด์ง€(๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…)๊ฐ€ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค."

---

### Step 2: ํ•ต์‹ฌ ๋ชจ๋ธ ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ

**ํ”„๋กฌํ”„ํŠธ:**

๋‹ค์Œ ๋ชจ๋ธ๋“ค์„ ์ƒ์„ฑํ•˜๊ณ  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ž‘์„ฑํ•˜์„ธ์š”.

โ–  Pet (์• ๊ฒฌ ํ”„๋กœํ•„) ๋ชจ๋ธ:

  • id (primary key)
  • user_id (foreign key, users ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • name (string, ์• ๊ฒฌ ์ด๋ฆ„)
  • breed (string, nullable, ๊ฒฌ์ข…)
  • birth_date (date, nullable, ์ƒ๋…„์›”์ผ)
  • photo (string, nullable, ํ”„๋กœํ•„ ์‚ฌ์ง„ ๊ฒฝ๋กœ)
  • timestamps

โ–  Diary (์• ๊ฒฌ์ผ์ง€) ๋ชจ๋ธ:

  • id (primary key)
  • user_id (foreign key, users ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • pet_id (foreign key, nullable, pets ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • title (string, ์ œ๋ชฉ)
  • content (text, ๋‚ด์šฉ)
  • is_public (boolean, default: false, ๊ณต๊ฐœ ์—ฌ๋ถ€)
  • published_at (timestamp, nullable, ๊ณต๊ฐœ ์‹œ์ )
  • timestamps

โ–  DiaryImage (์• ๊ฒฌ์ผ์ง€ ์ด๋ฏธ์ง€) ๋ชจ๋ธ:

  • id (primary key)
  • diary_id (foreign key, diaries ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • image_path (string, ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ)
  • sort_order (integer, default: 0)
  • timestamps

โ–  Category (์ปค๋ฎค๋‹ˆํ‹ฐ ์นดํ…Œ๊ณ ๋ฆฌ) ๋ชจ๋ธ:

  • id (primary key)
  • name (string, ์นดํ…Œ๊ณ ๋ฆฌ๋ช…)
  • slug (string, unique, URL์šฉ ์Šฌ๋Ÿฌ๊ทธ)
  • sort_order (integer, default: 0)
  • is_active (boolean, default: true)
  • timestamps

โ–  Post (์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œ๊ธ€) ๋ชจ๋ธ:

  • id (primary key)
  • user_id (foreign key, users ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • category_id (foreign key, categories ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • title (string, ์ œ๋ชฉ)
  • content (text, ๋‚ด์šฉ)
  • view_count (integer, default: 0)
  • timestamps

โ–  Comment (๋Œ“๊ธ€) ๋ชจ๋ธ:

  • id (primary key)
  • user_id (foreign key, users ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • post_id (foreign key, posts ํ…Œ์ด๋ธ” ์ฐธ์กฐ)
  • content (text, ๋Œ“๊ธ€ ๋‚ด์šฉ)
  • timestamps

โ–  Notice (๊ณต์ง€์‚ฌํ•ญ) ๋ชจ๋ธ:

  • id (primary key)
  • user_id (foreign key, users ํ…Œ์ด๋ธ” ์ฐธ์กฐ, ์ž‘์„ฑ ๊ด€๋ฆฌ์ž)
  • title (string, ์ œ๋ชฉ)
  • content (text, ๋‚ด์šฉ)
  • is_pushed (boolean, default: false, ํ‘ธ์‹œ ๋ฐœ์†ก ์—ฌ๋ถ€)
  • timestamps

๋ชจ๋ธ ๊ฐ„ ๊ด€๊ณ„ ์„ค์ •:

  • User hasMany Pet, Diary, Post, Comment
  • Pet belongsTo User, hasMany Diary
  • Diary belongsTo User, belongsTo Pet, hasMany DiaryImage
  • DiaryImage belongsTo Diary
  • Category hasMany Post
  • Post belongsTo User, belongsTo Category, hasMany Comment
  • Comment belongsTo User, belongsTo Post

users ํ…Œ์ด๋ธ”์— is_admin (boolean, default: false) ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋„ ์ž‘์„ฑํ•˜์„ธ์š”.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜๊ณ , ํ…Œ์ŠคํŠธ์šฉ ์‹œ๋”๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”:

  • ์ผ๋ฐ˜ ์œ ์ € 3๋ช…, ๊ด€๋ฆฌ์ž 1๋ช…
  • ๊ฐ ์œ ์ €์— ์• ๊ฒฌ 1~2๋งˆ๋ฆฌ
  • ์• ๊ฒฌ์ผ์ง€ ๊ฐ 5๊ฐœ (์ผ๋ถ€๋Š” ๊ณต๊ฐœ)
  • ์นดํ…Œ๊ณ ๋ฆฌ 4๊ฐœ (๊ฑด๊ฐ•/์˜์–‘, ํ›ˆ๋ จ/๊ต์œก, ์šฉํ’ˆ ํ›„๊ธฐ, ์ž์œ ๊ฒŒ์‹œํŒ)
  • ๊ฒŒ์‹œ๊ธ€ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ 3๊ฐœ์”ฉ, ๋Œ“๊ธ€ ๊ฐ 2๊ฐœ
  • ๊ณต์ง€์‚ฌํ•ญ 2๊ฐœ

php artisan make:model Pet -m php artisan make:model Diary -m php artisan make:model DiaryImage -m php artisan make:model Category -m php artisan make:model Post -m php artisan make:model Comment -m php artisan make:model Notice -m php artisan migrate:fresh --seed


**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ:** "๋ชจ๋“  ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๊ณ , ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ(์œ ์ € 4๋ช…, ์• ๊ฒฌ, ์ผ์ง€, ์นดํ…Œ๊ณ ๋ฆฌ 4๊ฐœ, ๊ฒŒ์‹œ๊ธ€, ๋Œ“๊ธ€, ๊ณต์ง€์‚ฌํ•ญ)๊ฐ€ ์‹œ๋”ฉ๋ฉ๋‹ˆ๋‹ค."

---

### Step 3: ์• ๊ฒฌ ํ”„๋กœํ•„ ๋ฐ ์• ๊ฒฌ์ผ์ง€ ๊ธฐ๋Šฅ

**ํ”„๋กฌํ”„ํŠธ:**

์• ๊ฒฌ ํ”„๋กœํ•„ ๊ด€๋ฆฌ์™€ ์• ๊ฒฌ์ผ์ง€ CRUD ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (PetController):

  • index(): ๋กœ๊ทธ์ธ ์œ ์ €์˜ ์• ๊ฒฌ ๋ชฉ๋ก ์กฐํšŒ
  • store(): ์• ๊ฒฌ ๋“ฑ๋ก (์ด๋ฆ„, ๊ฒฌ์ข…, ์ƒ๋…„์›”์ผ, ์‚ฌ์ง„ ์—…๋กœ๋“œ)
  • update(): ์• ๊ฒฌ ์ •๋ณด ์ˆ˜์ •
  • destroy(): ์• ๊ฒฌ ์‚ญ์ œ

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (DiaryController):

  • index(): ๋กœ๊ทธ์ธ ์œ ์ €์˜ ๋น„๊ณต๊ฐœ ์• ๊ฒฌ์ผ์ง€ ๋ชฉ๋ก (is_public=false ํฌํ•จ ์ „์ฒด)
  • create(): ์ผ์ง€ ์ž‘์„ฑ ํผ
  • store(): ์ผ์ง€ ์ €์žฅ (์ œ๋ชฉ, ๋‚ด์šฉ, ์• ๊ฒฌ ์„ ํƒ, ์ด๋ฏธ์ง€ ๋‹ค์ค‘ ์—…๋กœ๋“œ)
  • show(): ์ผ์ง€ ์ƒ์„ธ ๋ณด๊ธฐ (๋ณธ์ธ ์ผ์ง€ ๋˜๋Š” ๊ณต๊ฐœ ์ผ์ง€๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ)
  • edit(): ์ผ์ง€ ์ˆ˜์ • ํผ
  • update(): ์ผ์ง€ ์ˆ˜์ •
  • destroy(): ์ผ์ง€ ์‚ญ์ œ
  • publish(): ์ผ์ง€ ๊ณต๊ฐœ ์ฒ˜๋ฆฌ (is_public=true, published_at=now())
  • unpublish(): ์ผ์ง€ ๋น„๊ณต๊ฐœ ์ „ํ™˜ (is_public=false)

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (PublicDiaryController):

  • index(): ๊ณต๊ฐœ๋œ ์• ๊ฒฌ์ผ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ (is_public=true์ธ ์ผ์ง€๋งŒ, ์ตœ์‹ ์ˆœ, ํŽ˜์ด์ง€๋„ค์ด์…˜)
  • show(): ๊ณต๊ฐœ ์ผ์ง€ ์ƒ์„ธ ๋ณด๊ธฐ

โ–  Blade ๋ทฐ ๊ตฌ์„ฑ:

  • resources/views/pets/index.blade.php: ๋‚ด ์• ๊ฒฌ ๋ชฉ๋ก + ๋“ฑ๋ก ํผ (Alpine.js ๋ชจ๋‹ฌ)
  • resources/views/diaries/index.blade.php: ๋‚ด ์• ๊ฒฌ์ผ์ง€ ๋ชฉ๋ก (๊ณต๊ฐœ/๋น„๊ณต๊ฐœ ๋ฐฐ์ง€ ํ‘œ์‹œ)
  • resources/views/diaries/create.blade.php: ์ผ์ง€ ์ž‘์„ฑ ํผ (์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํฌํ•จ)
  • resources/views/diaries/show.blade.php: ์ผ์ง€ ์ƒ์„ธ (์ด๋ฏธ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ)
  • resources/views/diaries/edit.blade.php: ์ผ์ง€ ์ˆ˜์ • ํผ
  • resources/views/diaries/public/index.blade.php: ๊ณต๊ฐœ ์ผ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ (์นด๋“œ ๊ทธ๋ฆฌ๋“œ)
  • resources/views/diaries/public/show.blade.php: ๊ณต๊ฐœ ์ผ์ง€ ์ƒ์„ธ

โ–  ๋ผ์šฐํŠธ ์„ค์ • (routes/web.php): Route::middleware('auth')->group(function () { Route::resource('pets', PetController::class)->except(['show']); Route::resource('diaries', DiaryController::class); Route::patch('/diaries/{diary}/publish', [DiaryController::class, 'publish'])->name('diaries.publish'); Route::patch('/diaries/{diary}/unpublish', [DiaryController::class, 'unpublish'])->name('diaries.unpublish'); }); Route::get('/public-diaries', [PublicDiaryController::class, 'index'])->name('public-diaries.index'); Route::get('/public-diaries/{diary}', [PublicDiaryController::class, 'show'])->name('public-diaries.show');

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋Š” Storage::disk('public')์— ์ €์žฅํ•˜๊ณ , php artisan storage:link ๋กœ ์‹ฌ๋ณผ๋ฆญ ๋งํฌ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.

์ผ์ง€ ์ž‘์„ฑ ์‹œ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” Alpine.js๋กœ ๊ตฌํ˜„ํ•˜์„ธ์š”. ๊ณต๊ฐœ/๋น„๊ณต๊ฐœ ํ† ๊ธ€ ๋ฒ„ํŠผ๋„ Alpine.js๋กœ ํ™•์ธ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ํ‘œ์‹œํ•œ ํ›„ ๋™์ž‘ํ•˜๊ฒŒ ํ•˜์„ธ์š”.

Tailwind CSS๋กœ ๊น”๋”ํ•œ ์นด๋“œ ๋ ˆ์ด์•„์›ƒ์„ ๊ตฌ์„ฑํ•˜์„ธ์š”.


**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ:** "์• ๊ฒฌ ํ”„๋กœํ•„์„ ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œํ•  ์ˆ˜ ์žˆ๊ณ , ์• ๊ฒฌ์ผ์ง€๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ๊ธฐ๋ณธ ๋น„๊ณต๊ฐœ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ๊ณต๊ฐœ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๊ณต๊ฐœ ๊ฐค๋Ÿฌ๋ฆฌ์— ๋…ธ์ถœ๋˜๊ณ , ๋น„๊ณต๊ฐœ๋กœ ๋‹ค์‹œ ์ „ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."

---

### Step 4: ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œํŒ ๋ฐ ๋Œ“๊ธ€ ๊ธฐ๋Šฅ

**ํ”„๋กฌํ”„ํŠธ:**

์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œํŒ๊ณผ ๋Œ“๊ธ€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (PostController):

  • index(): ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก (์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ, ๊ฒ€์ƒ‰, ํŽ˜์ด์ง€๋„ค์ด์…˜)
  • create(): ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ํผ (์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ)
  • store(): ๊ฒŒ์‹œ๊ธ€ ์ €์žฅ
  • show(): ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ + ๋Œ“๊ธ€ ๋ชฉ๋ก
  • edit(): ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • (๋ณธ์ธ ๊ธ€๋งŒ)
  • update(): ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ์ฒ˜๋ฆฌ
  • destroy(): ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ (๋ณธ์ธ ๊ธ€ ๋˜๋Š” ๊ด€๋ฆฌ์ž)

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (CommentController):

  • store(): ๋Œ“๊ธ€ ์ž‘์„ฑ (post_id, content)
  • update(): ๋Œ“๊ธ€ ์ˆ˜์ • (๋ณธ์ธ ๋Œ“๊ธ€๋งŒ)
  • destroy(): ๋Œ“๊ธ€ ์‚ญ์ œ (๋ณธ์ธ ๋Œ“๊ธ€ ๋˜๋Š” ๊ด€๋ฆฌ์ž)

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (Admin\CategoryController):

  • index(): ์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ ํŽ˜์ด์ง€
  • store(): ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€
  • update(): ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜์ • (์ด๋ฆ„, ์Šฌ๋Ÿฌ๊ทธ, ์ •๋ ฌ์ˆœ์„œ, ํ™œ์„ฑํ™” ์—ฌ๋ถ€)
  • destroy(): ์นดํ…Œ๊ณ ๋ฆฌ ์‚ญ์ œ (๊ฒŒ์‹œ๊ธ€์ด ์žˆ์œผ๋ฉด ์‚ญ์ œ ๋ถˆ๊ฐ€, ๊ฒฝ๊ณ  ํ‘œ์‹œ)

โ–  Blade ๋ทฐ ๊ตฌ์„ฑ:

  • resources/views/posts/index.blade.php:
    • ์ƒ๋‹จ์— ์นดํ…Œ๊ณ ๋ฆฌ ํƒญ (์ „์ฒด + ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ)
    • ๊ฒ€์ƒ‰ ๋ฐ” (์ œ๋ชฉ+๋‚ด์šฉ ๊ฒ€์ƒ‰)
    • ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ (์ œ๋ชฉ, ์ž‘์„ฑ์ž, ์นดํ…Œ๊ณ ๋ฆฌ, ์ž‘์„ฑ์ผ, ์กฐํšŒ์ˆ˜)
    • ํŽ˜์ด์ง€๋„ค์ด์…˜
  • resources/views/posts/create.blade.php: ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ ํผ
  • resources/views/posts/show.blade.php:
    • ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ๋‚ด์šฉ
    • ํ•˜๋‹จ์— ๋Œ“๊ธ€ ๋ชฉ๋ก
    • ๋Œ“๊ธ€ ์ž‘์„ฑ ํผ
    • ๋Œ“๊ธ€ ์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ (๋ณธ์ธ ๋Œ“๊ธ€์—๋งŒ ํ‘œ์‹œ)
  • resources/views/posts/edit.blade.php: ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ํผ
  • resources/views/admin/categories/index.blade.php:
    • ์นดํ…Œ๊ณ ๋ฆฌ ๋ชฉ๋ก (๋“œ๋ž˜๊ทธ ์ •๋ ฌ ๋˜๋Š” ์ˆœ์„œ ์ž…๋ ฅ)
    • ์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ

โ–  ๋ผ์šฐํŠธ ์„ค์ •: Route::middleware('auth')->group(function () { Route::resource('posts', PostController::class); Route::post('/posts/{post}/comments', [CommentController::class, 'store'])->name('comments.store'); Route::put('/comments/{comment}', [CommentController::class, 'update'])->name('comments.update'); Route::delete('/comments/{comment}', [CommentController::class, 'destroy'])->name('comments.destroy'); });

// ๊ด€๋ฆฌ์ž ์ „์šฉ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () { Route::resource('categories', Admin\CategoryController::class)->except(['show', 'create', 'edit']); });

admin ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:

  • App\Http\Middleware\AdminMiddleware
  • is_admin์ด true์ธ ์œ ์ €๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ
  • bootstrap/app.php์— 'admin' ๋ณ„์นญ์œผ๋กœ ๋“ฑ๋ก

๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ ‘๊ทผ ์‹œ view_count๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚ค์„ธ์š”. ๋Œ“๊ธ€ ์ˆ˜์ •์€ Alpine.js๋กœ ์ธ๋ผ์ธ ํŽธ์ง‘ ๋ชจ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

Tailwind CSS๋กœ ๊ฒŒ์‹œํŒ ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ๊ณผ ์นดํ…Œ๊ณ ๋ฆฌ ํƒญ UI๋ฅผ ๊ตฌ์„ฑํ•˜์„ธ์š”.


**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ:** "์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œํŒ์—์„œ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๊ฒŒ์‹œ๊ธ€์„ ์กฐํšŒํ•˜๊ณ , ์ž‘์„ฑ/์ˆ˜์ •/์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋Œ“๊ธ€ ์ž‘์„ฑ๊ณผ ์ธ๋ผ์ธ ์ˆ˜์ •/์‚ญ์ œ๊ฐ€ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ฆฌ์ž๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."

---

### Step 5: ๊ณต์ง€์‚ฌํ•ญ ๋ฐ ์›น ํ‘ธ์‹œ ์•Œ๋ฆผ

**ํ”„๋กฌํ”„ํŠธ:**

๊ณต์ง€์‚ฌํ•ญ ๊ธฐ๋Šฅ๊ณผ ์›น ํ‘ธ์‹œ ์•Œ๋ฆผ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

โ–  ์›น ํ‘ธ์‹œ ํŒจํ‚ค์ง€ ์„ค์น˜: composer require laravel-notification-channels/webpush php artisan vendor:publish --provider="NotificationChannels\WebPush\WebPushServiceProvider" --tag="migrations" php artisan migrate

User ๋ชจ๋ธ์— NotificationChannels\WebPush\HasPushSubscriptions ํŠธ๋ ˆ์ดํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

VAPID ํ‚ค ์ƒ์„ฑ: php artisan webpush:vapid ์ƒ์„ฑ๋œ ํ‚ค๋ฅผ .env์— ์„ค์ •ํ•˜์„ธ์š”.

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (NoticeController):

  • index(): ๊ณต์ง€์‚ฌํ•ญ ๋ชฉ๋ก (๋ชจ๋“  ์œ ์ € ์ ‘๊ทผ ๊ฐ€๋Šฅ, ํŽ˜์ด์ง€๋„ค์ด์…˜)
  • show(): ๊ณต์ง€์‚ฌํ•ญ ์ƒ์„ธ
  • create(): ๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ ํผ (๊ด€๋ฆฌ์ž๋งŒ)
  • store(): ๊ณต์ง€์‚ฌํ•ญ ์ €์žฅ + ์›น ํ‘ธ์‹œ ๋ฐœ์†ก (๊ด€๋ฆฌ์ž๋งŒ)
  • edit(): ๊ณต์ง€์‚ฌํ•ญ ์ˆ˜์ • ํผ (๊ด€๋ฆฌ์ž๋งŒ)
  • update(): ๊ณต์ง€์‚ฌํ•ญ ์ˆ˜์ • (๊ด€๋ฆฌ์ž๋งŒ)
  • destroy(): ๊ณต์ง€์‚ฌํ•ญ ์‚ญ์ œ (๊ด€๋ฆฌ์ž๋งŒ)

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (PushSubscriptionController):

  • store(): ํ‘ธ์‹œ ๊ตฌ๋… ๋“ฑ๋ก (๋ธŒ๋ผ์šฐ์ €์—์„œ ์ „์†กํ•œ subscription ์ •๋ณด ์ €์žฅ)
  • destroy(): ํ‘ธ์‹œ ๊ตฌ๋… ํ•ด์ œ

โ–  Laravel Notification (NewNoticeNotification):

  • toWebPush($notifiable, $notification) ๋ฉ”์„œ๋“œ:
    • ์ œ๋ชฉ: ๊ณต์ง€์‚ฌํ•ญ ์ œ๋ชฉ
    • ๋ณธ๋ฌธ: ๊ณต์ง€์‚ฌํ•ญ ๋‚ด์šฉ ์•ž 100์ž
    • ์•ก์…˜: ๊ณต์ง€์‚ฌํ•ญ ์ƒ์„ธ ํŽ˜์ด์ง€ URL
  • via() ๋ฉ”์„œ๋“œ: ['webpush'] ๋ฐ˜ํ™˜

โ–  Blade ๋ทฐ ๊ตฌ์„ฑ:

  • resources/views/notices/index.blade.php: ๊ณต์ง€์‚ฌํ•ญ ๋ชฉ๋ก (์ œ๋ชฉ, ์ž‘์„ฑ์ผ)
  • resources/views/notices/show.blade.php: ๊ณต์ง€์‚ฌํ•ญ ์ƒ์„ธ
  • resources/views/notices/create.blade.php: ๊ณต์ง€์‚ฌํ•ญ ์ž‘์„ฑ ํผ (๊ด€๋ฆฌ์ž)
  • resources/views/notices/edit.blade.php: ๊ณต์ง€์‚ฌํ•ญ ์ˆ˜์ • ํผ (๊ด€๋ฆฌ์ž)

โ–  ์„œ๋น„์Šค ์›Œ์ปค ๋ฐ ํ‘ธ์‹œ ๊ตฌ๋… JavaScript:

  • public/sw.js: ์„œ๋น„์Šค ์›Œ์ปค (ํ‘ธ์‹œ ์ˆ˜์‹  ๋ฐ ์•Œ๋ฆผ ํ‘œ์‹œ, ์•Œ๋ฆผ ํด๋ฆญ ์‹œ ํ•ด๋‹น URL๋กœ ์ด๋™)
  • resources/views/layouts/app.blade.php์— ํ‘ธ์‹œ ๊ตฌ๋… ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€:
    • ๋กœ๊ทธ์ธํ•œ ์œ ์ €์—๊ฒŒ ์•Œ๋ฆผ ๊ถŒํ•œ ์š”์ฒญ
    • ํ—ˆ์šฉ ์‹œ PushManager.subscribe()๋กœ ๊ตฌ๋… ์ƒ์„ฑ
    • ๊ตฌ๋… ์ •๋ณด๋ฅผ /push-subscriptions๋กœ POST ์ „์†ก

โ–  ๋ผ์šฐํŠธ ์„ค์ •: Route::get('/notices', [NoticeController::class, 'index'])->name('notices.index'); Route::get('/notices/{notice}', [NoticeController::class, 'show'])->name('notices.show');

Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () { Route::get('/notices/create', [NoticeController::class, 'create'])->name('notices.create'); Route::post('/notices', [NoticeController::class, 'store'])->name('notices.store'); Route::get('/notices/{notice}/edit', [NoticeController::class, 'edit'])->name('notices.edit'); Route::put('/notices/{notice}', [NoticeController::class, 'update'])->name('notices.update'); Route::delete('/notices/{notice}', [NoticeController::class, 'destroy'])->name('notices.destroy'); });

Route::middleware('auth')->group(function () { Route::post('/push-subscriptions', [PushSubscriptionController::class, 'store']); Route::delete('/push-subscriptions', [PushSubscriptionController::class, 'destroy']); });

๊ณต์ง€์‚ฌํ•ญ ์ €์žฅ ์‹œ store() ๋ฉ”์„œ๋“œ์—์„œ ๋ชจ๋“  ํ‘ธ์‹œ ๊ตฌ๋… ์œ ์ €์—๊ฒŒ NewNoticeNotification์„ ๋ฐœ์†กํ•˜์„ธ์š”. ๋Œ€๋Ÿ‰ ๋ฐœ์†ก ์‹œ ํ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ShouldQueue ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.


**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ:** "๊ด€๋ฆฌ์ž๊ฐ€ ๊ณต์ง€์‚ฌํ•ญ์„ ์ž‘์„ฑํ•˜๋ฉด ์›น ํ‘ธ์‹œ ์•Œ๋ฆผ์ด ๊ตฌ๋… ์œ ์ €์—๊ฒŒ ๋ฐœ์†ก๋ฉ๋‹ˆ๋‹ค. ์œ ์ €๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•Œ๋ฆผ ํ—ˆ์šฉ ์‹œ ์ž๋™์œผ๋กœ ๊ตฌ๋…๋˜๋ฉฐ, ์•Œ๋ฆผ์„ ํด๋ฆญํ•˜๋ฉด ๊ณต์ง€์‚ฌํ•ญ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค."

---

### Step 6: ๋ฉ”์ธ ํŽ˜์ด์ง€, ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ ๋งˆ๋ฌด๋ฆฌ

**ํ”„๋กฌํ”„ํŠธ:**

๋ฉ”์ธ(ํ™ˆ) ํŽ˜์ด์ง€, ํ†ตํ•ฉ ๋„ค๋น„๊ฒŒ์ด์…˜, ๊ทธ๋ฆฌ๊ณ  ์ „์ฒด์ ์ธ UI ๋งˆ๋ฌด๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.

โ–  Laravel ์ปจํŠธ๋กค๋Ÿฌ (HomeController):

  • index(): ๋ฉ”์ธ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ
    • ์ตœ์‹  ๊ณต์ง€์‚ฌํ•ญ 3๊ฐœ
    • ์ตœ์‹  ๊ณต๊ฐœ ์• ๊ฒฌ์ผ์ง€ 6๊ฐœ (์ธ๋„ค์ผ ํฌํ•จ)
    • ์ธ๊ธฐ ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œ๊ธ€ 5๊ฐœ (์กฐํšŒ์ˆ˜ ๊ธฐ์ค€)
    • ์ตœ์‹  ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ฒŒ์‹œ๊ธ€ 5๊ฐœ

โ–  Blade ๋ทฐ ๊ตฌ์„ฑ:

  • resources/views/home.blade.php:
    • ํžˆ์–ด๋กœ ์„น์…˜ (์‚ฌ์ดํŠธ ์†Œ๊ฐœ, ๊ฐ•์•„์ง€ ์ผ๋Ÿฌ์ŠคํŠธ ๋˜๋Š” ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€)
    • ์ตœ์‹  ๊ณต์ง€์‚ฌํ•ญ ์Šฌ๋ผ์ด๋“œ ๋˜๋Š” ๋ฆฌ์ŠคํŠธ
    • ๊ณต๊ฐœ ์• ๊ฒฌ์ผ์ง€ ์นด๋“œ ๊ทธ๋ฆฌ๋“œ (์‚ฌ์ง„ ์ธ๋„ค์ผ, ์ œ๋ชฉ, ์ž‘์„ฑ์ž)
    • ์ธ๊ธฐ ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ
    • ์ตœ์‹  ๊ฒŒ์‹œ๊ธ€ ๋ฆฌ์ŠคํŠธ

โ–  ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ ˆ์ด์•„์›ƒ ์ˆ˜์ • (resources/views/layouts/navigation.blade.php):

  • ๋กœ๊ณ : "๋ฉ๋ฉ์ปค๋ฎค๋‹ˆํ‹ฐ"
  • ๋ฉ”๋‰ด: ํ™ˆ, ๊ณต๊ฐœ์ผ์ง€, ์ปค๋ฎค๋‹ˆํ‹ฐ, ๊ณต์ง€์‚ฌํ•ญ
  • ๋กœ๊ทธ์ธ ์œ ์ €: ๋‚ด ์ผ์ง€, ๋‚ด ์• ๊ฒฌ ๊ด€๋ฆฌ (๋“œ๋กญ๋‹ค์šด)
  • ๊ด€๋ฆฌ์ž: ๊ด€๋ฆฌ ๋ฉ”๋‰ด (์นดํ…Œ๊ณ ๋ฆฌ ๊ด€๋ฆฌ, ๊ณต์ง€์‚ฌํ•ญ ๊ด€๋ฆฌ)
  • ๋ชจ๋ฐ”์ผ ํ–„๋ฒ„๊ฑฐ ๋ฉ”๋‰ด

โ–  ๋ผ์šฐํŠธ ์„ค์ •: Route::get('/', [HomeController::class, 'index'])->name('home');

โ–  ์ถ”๊ฐ€ ๋งˆ๋ฌด๋ฆฌ ์ž‘์—…:

  • 404, 403 ์—๋Ÿฌ ํŽ˜์ด์ง€ ์ปค์Šคํ„ฐ๋งˆ์ด์ง• (resources/views/errors/)
  • ํ”Œ๋ž˜์‹œ ๋ฉ”์‹œ์ง€ ์ปดํฌ๋„ŒํŠธ (์„ฑ๊ณต/์—๋Ÿฌ ์•Œ๋ฆผ ํ† ์ŠคํŠธ, Alpine.js ์ž๋™ ๋‹ซ๊ธฐ)
  • ๋ฉ”ํƒ€ ํƒœ๊ทธ ์„ค์ • (SEO ๊ธฐ๋ณธ, Open Graph ํƒœ๊ทธ)
  • ํ”„๋กœํ•„ ํŽ˜์ด์ง€์—์„œ ๋‹‰๋„ค์ž„ ์ˆ˜์ • ๊ธฐ๋Šฅ ์ถ”๊ฐ€ (users ํ…Œ์ด๋ธ”์— nickname ์ปฌ๋Ÿผ ์ถ”๊ฐ€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜)
  • ์ „์ฒด ํŽ˜์ด์ง€ ๋ฐ˜์‘ํ˜• ์ ๊ฒ€ (๋ชจ๋ฐ”์ผ, ํƒœ๋ธ”๋ฆฟ, ๋ฐ์Šคํฌํƒ‘)

Tailwind CSS๋กœ ๊ฐ•์•„์ง€ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์–ด์šธ๋ฆฌ๋Š” ๋”ฐ๋œปํ•œ ํ†ค์˜ ์ปฌ๋Ÿฌ ํ…Œ๋งˆ๋ฅผ ์ ์šฉํ•˜์„ธ์š”:

  • Primary: amber/orange ๊ณ„์—ด
  • ๋ฐฐ๊ฒฝ: warm gray ๊ณ„์—ด
  • ์นด๋“œ: ๋‘ฅ๊ทผ ๋ชจ์„œ๋ฆฌ, ๋ถ€๋“œ๋Ÿฌ์šด ๊ทธ๋ฆผ์ž

tailwind.config.js์— ์ปค์Šคํ…€ ์ปฌ๋Ÿฌ๋ฅผ ์„ค์ •ํ•˜์„ธ์š”.


**๊ธฐ๋Œ€ ๊ฒฐ๊ณผ:** "๋ฉ”์ธ ํŽ˜์ด์ง€์— ์ตœ์‹  ์ฝ˜ํ…์ธ ๊ฐ€ ์ข…ํ•ฉ์ ์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ , ๋„ค๋น„๊ฒŒ์ด์…˜์„ ํ†ตํ•ด ๋ชจ๋“  ๊ธฐ๋Šฅ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์ด ๋ชจ๋ฐ”์ผ์—์„œ๋„ ์ž˜ ๋™์ž‘ํ•˜๋ฉฐ, ํ–ฅํ›„ ๋ชจ๋ฐ”์ผ ์•ฑ ์ „ํ™˜์ด ์šฉ์ดํ•œ ๊ตฌ์กฐ๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค."

---

## ๋ชจ๋ฐ”์ผ ์•ฑ ์ „ํ™˜ ๊ฐ€์ด๋“œ (ํ–ฅํ›„ ์ฐธ๊ณ )

์›น ์™„์„ฑ ํ›„ ๋ชจ๋ฐ”์ผ ์•ฑ ์ „ํ™˜ ์‹œ ๋‹ค์Œ ๋ฐฉ๋ฒ•์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค:

1. **API ๋ผ์šฐํŠธ ์ถ”๊ฐ€**: ๊ธฐ์กด ์ปจํŠธ๋กค๋Ÿฌ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•˜์—ฌ `routes/api.php`์— JSON ์‘๋‹ต API๋ฅผ ์ถ”๊ฐ€
2. **Laravel Sanctum**์œผ๋กœ ๋ชจ๋ฐ”์ผ ์•ฑ ํ† ํฐ ์ธ์ฆ ๊ตฌํ˜„
3. **React Native** ๋˜๋Š” **Flutter**๋กœ ๋ชจ๋ฐ”์ผ ํด๋ผ์ด์–ธํŠธ ๊ฐœ๋ฐœ
4. ๋˜๋Š” **PWA(Progressive Web App)** ๋ฐฉ์‹์œผ๋กœ ํ˜„์žฌ ์›น์„ ์•ฑ์ฒ˜๋Ÿผ ์„ค์น˜ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๊ตฌ์„ฑ (์„œ๋น„์Šค ์›Œ์ปค ์ด๋ฏธ ๊ตฌํ˜„๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ manifest.json ์ถ”๊ฐ€๋งŒ์œผ๋กœ ๊ฐ€๋Šฅ)

์œ„ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๊ฒŒ์‹œ๋ฌผ์˜ ์ œ๋ชฉ/๋‚ด์šฉ ํ•„๋“œ์— ๋ถ™์—ฌ๋„ฃ์œผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. Laravel ๋ฒ„์ „์„ 13.x๋กœ ๋ชจ๋‘ ์ˆ˜์ •ํ–ˆ๊ณ , ์ฐธ๊ณ  ๋ ˆ์‹œํ”ผ์™€ ๋™์ผํ•œ ํ˜•์‹(ํ”„๋กœ์ ํŠธ ๊ฐœ์š” โ†’ ๊ธฐ๋Šฅ โ†’ ๊ธฐ์ˆ  ์Šคํƒ โ†’ ์ œ์•ฝ ์กฐ๊ฑด โ†’ Step๋ณ„ ํ”„๋กฌํ”„ํŠธ + ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ)์œผ๋กœ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด 1๋ช…์ด ๋ฐ˜์‘ํ–ˆ์Šต๋‹ˆ๋‹ค
|

๋Œ“๊ธ€ 2๊ฐœ

์œ„ ๋ ˆ์‹œํ”ผ๋Š” ์ œ๋Œ€๋กœ ์ž‘๋™์„ ํ•˜๋„ค์š”
์ธ๋„ค์ผ๋“ฑ์„ ์ฒจ๋ถ€ํ•˜๋Š” ์•ฝ๊ฐ„์˜ ์ž‘์—…์„ ํ•ด์„œ ๋งˆ๋ฌด๋ฆฌ ํ–ˆ์Šต๋‹ˆ๋‹ค
1์‹œ๊ฐ„ ์ •๋„ ๊ฑธ๋ฆฐ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค
https://sir.kr/showcases/199
๋งŽ์ด ๋ฐฐ์šฐ๊ณ  ๊ฐ‘๋‹ˆ๋‹ค!
๋Œ“๊ธ€์„ ์ž‘์„ฑํ•˜์‹œ๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

AI

624๊ฑด
+
์ œ๋ชฉ ๊ธ€์“ด์ด ๋‚ ์งœ ์กฐํšŒ
04-07 ์กฐํšŒ 459
04-06 ์กฐํšŒ 393
04-06 ์กฐํšŒ 541
04-05 ์กฐํšŒ 376
04-04 ์กฐํšŒ 531
04-03 ์กฐํšŒ 740
04-03 ์กฐํšŒ 407
04-02 ์กฐํšŒ 434
04-02 ์กฐํšŒ 488
04-02 ์กฐํšŒ 549
04-01 ์กฐํšŒ 354
03-31 ์กฐํšŒ 377
03-30 ์กฐํšŒ 456
03-30 ์กฐํšŒ 514
03-30 ์กฐํšŒ 390
03-29 ์กฐํšŒ 462
03-29 ์กฐํšŒ 386
03-28 ์กฐํšŒ 367
03-28 ์กฐํšŒ 444
03-28 ์กฐํšŒ 441
03-27 ์กฐํšŒ 420
03-27 ์กฐํšŒ 468
03-27 ์กฐํšŒ 401
03-27 ์กฐํšŒ 341
03-27 ์กฐํšŒ 357
03-26 ์กฐํšŒ 346
03-26 ์กฐํšŒ 387
03-26 ์กฐํšŒ 434
03-26 ์กฐํšŒ 362
03-26 ์กฐํšŒ 416