본문 바로가기
코딩(php,javascript,mysql)

한눈에 보는 PHP 역사 요약, 한번에 알아보자~

by mylife365 2025. 6. 21.

PHP의 역사는 1990년대 중반부터 시작된 오픈소스 서버사이드 스크립트 언어의 발전사로, 웹 개발 트렌드에 따라 큰 변화를 겪어왔습니다. 아래는 한눈에 보는 PHP 역사 요약입니다:


🕰️ 한눈에 보는 PHP 역사 (버전 중심 요약)

출시년도 버전 주요 특징 및 변화

1995 PHP/FI Rasmus Lerdorf가 C언어로 CGI 기반 툴 개발 (Personal Home Page Tools)
1997 PHP 3 PHP로 이름 변경, MySQL 등 다양한 DB 지원, HTML 내 스크립트 삽입 가능
2000 PHP 4 Zend Engine 1.0 기반, 성능 향상, 세션 관리 도입
2004 PHP 5 Zend Engine 2.0, OOP(객체지향) 본격 지원, PDO, 예외 처리 도입
2015 PHP 7.0 PHP 6 실패 후 대대적 개편, 성능 2배 향상, 스칼라 타입 선언, null 병합 연산자
2016~2019 PHP 7.1~7.4 iterable, void 타입, arrow function, typed properties 등 점진적 개선
2020 PHP 8.0 JIT 컴파일 도입, 속도 향상, match, union types, 속성(Attribute) 문법
2021 PHP 8.1 readonly, enum 지원, fibers(비동기 기능 기반) 도입
2022 PHP 8.2 readonly class, disjunctive normal form, deprecated dynamic properties
2023 PHP 8.3 json_validate(), typed class constants, 속도 및 안정성 향상
예정 PHP 8.4 / PHP 9? 지속적 개선 중, 강력한 타입 시스템과 비동기성 강화 예고

 

 

📌 PHP의 주요 흐름 요약

  • 1995~2000 (탄생기): 개인 프로젝트에서 시작되어 전 세계 개발자들이 사용하는 스크립트 언어로 성장.
  • 2000~2014 (성장기): OOP 기반 도입 → 대규모 웹 프로젝트도 가능해짐 (워드프레스, phpBB 등).
  • 2015~현재 (도약기): PHP 7, 8에서 성능 향상과 함께 현대적 문법 지속 도입. Laravel 등의 프레임워크 등장으로 개발 편의성 강화.
  • 미래: 강한 정적 타입, JIT 활용, 비동기 처리까지 강화되어 Python, Node.js와의 경쟁 지속.

🛠️ PHP 대표 생태계

  • CMS: WordPress, Drupal, Joomla
  • 프레임워크: Laravel, Symfony, CodeIgniter, Yii
  • 호환성: Apache, Nginx, MySQL, MariaDB 등과 완벽 연동

✅ 요약 한줄 평

PHP는 태생은 단순했지만, 지금은 성능·유연성·생태계를 모두 갖춘 웹 백엔드 언어입니다.


 

Symfony(심포니)는 PHP로 작성된 오픈소스 웹 애플리케이션 프레임워크로, 대규모 프로젝트나 엔터프라이즈급 애플리케이션에 적합하도록 설계된 강력한 프레임워크입니다.


🧱 Symfony란?

  • 개발사: 프랑스 회사 SensioLabs가 2005년에 처음 개발
  • 라이선스: MIT License (상용 프로젝트에도 자유롭게 사용 가능)
  • 공식 사이트: https://symfony.com

🎯 주요 특징

특징 설명

모듈식 구조 (Bundles) 모든 기능이 번들 형태로 나뉘어 있어 필요한 기능만 선택해서 사용 가능
재사용 가능한 컴포넌트 HTTP Foundation, Routing, Form, Validator 등 개별 컴포넌트만 따로 사용 가능 (Laravel도 이 컴포넌트 일부 사용)
강력한 디버그 툴 Web Debug Toolbar, Profiler 등을 제공하여 개발 시 편리함
PSR-표준 준수 PHP-FIG 표준에 부합해 유지보수성, 확장성 높음
테스트 친화적 PHPUnit 기반 자동 테스트 환경 내장
ORM 지원 Doctrine ORM을 통한 강력한 DB 관리 지원
다국어 지원 (i18n) 국제화/현지화에 강력한 기능 제공

🛠️ Symfony 구성 예시

/src
  /Controller
  /Entity
  /Repository
/config
/templates
/public

📦 Laravel과의 관계

  • Laravel은 Symfony의 많은 컴포넌트를 기반으로 만들어졌습니다.
  • Laravel: 초보자 친화적 + 빠른 개발에 적합
  • Symfony: 대규모 시스템, 커스터마이징, 안정성 중시

📌 Symfony 사용 사례

  • Drupal CMS
  • Magento 2
  • Laravel 일부 컴포넌트
  • Prestashop
  • Spotify, BlaBlaCar, Dailymotion 등 유럽 중심 대규모 서비스들

✅ 한줄 요약

Symfony는 모듈화와 안정성을 갖춘 PHP의 엔터프라이즈급 프레임워크입니다.


 

아래는 Symfony vs Laravel을 구조, 철학, 사용성, 학습곡선 등 다양한 관점에서 비교한 표입니다.


🥊 Symfony vs Laravel 비교표

항목 Symfony Laravel

출시 연도 2005 2011
개발사 SensioLabs (프랑스) Taylor Otwell (미국)
목표 유연성, 확장성, 재사용성 중심의 프레임워크 간결함, 생산성, 초보자 친화성 중심
구조 철학 모듈형 (Bundles) – 모든 기능을 커스터마이징 가능 의견 있는 프레임워크 (Opinionated) – 정해진 방식에 따라 빠르게 개발
학습 곡선 높음 – 설정이 많고 유연성이 큰 만큼 복잡함 낮음~중간 – 직관적이고 문서가 잘 되어 있어 빠른 진입 가능
문서화 매우 상세하고 공식 문서 완성도 높음 공식 문서 + 커뮤니티 가이드가 풍부하고 친절
템플릿 엔진 Twig 사용 (안정적, 분리도 높음) Blade 사용 (Laravel 특화, 직관적)
ORM Doctrine (엔티티 중심, SQL 추상화 우수) Eloquent (모델 중심, 직관적)
커뮤니티 규모 작지만 전문적인 유럽 기반 커뮤니티 전 세계적으로 매우 활발하고 커다란 커뮤니티
속도 및 성능 PHP 8 기반에서 빠름, 최적화에 강함 PHP 8 기반에서 빠르며, Laravel Octane으로 성능 개선 가능
마이크로서비스 적합 (Symfony Flex 등) 기본적으로 모놀리식 구조지만 모듈화 가능
사용 예 Drupal, Magento2, Prestashop, BlaBlaCar, Dailymotion 등 Laravel 자체, Statamic CMS, Laravel Nova, Laravel Forge 등
사용 난이도 중상~고급자용 초보자~중급자용

 

✅ 선택 가이드 요약

사용자 유형 추천 프레임워크 이유

빠르게 웹 프로젝트를 시작하고 싶은 스타트업/개인 ✅ Laravel 빠른 개발, 친절한 문서, 다양한 생태계
복잡한 대규모 시스템을 구축하려는 기업/팀 ✅ Symfony 강력한 구조화, 유연한 아키텍처, 유지보수 용이
Symfony의 안정성과 구조를 활용하면서도 빠르게 개발하고 싶은 경우 ✅ Laravel (Symfony 컴포넌트 기반) Laravel은 Symfony 기반에 생산성 요소 추가

 

아래는 Symfony vs Laravel의 실제 코드 비교 예시입니다.


동일한 기능을 기준으로 비교해드릴게요:
📌 간단한 "게시글(Post)" 목록 불러오기를 가정합니다.


1. 📦 라우팅 설정

Symfony (config/routes.yaml)

post_index:
  path: /posts
  controller: App\Controller\PostController::index

Laravel (routes/web.php)

Route::get('/posts', [PostController::class, 'index']);

2. 🎮 컨트롤러 코드

Symfony (src/Controller/PostController.php)

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use App\Repository\PostRepository;

class PostController extends AbstractController
{
    public function index(PostRepository $postRepository): Response
    {
        $posts = $postRepository->findAll();

        return $this->render('post/index.html.twig', [
            'posts' => $posts,
        ]);
    }
}

Laravel (app/Http/Controllers/PostController.php)

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all();

        return view('post.index', compact('posts'));
    }
}

3. 📄 템플릿 (뷰) 파일

Symfony (templates/post/index.html.twig)

<h1>Posts</h1>
<ul>
  {% for post in posts %}
    <li>{{ post.title }}</li>
  {% endfor %}
</ul>

Laravel (resources/views/post/index.blade.php)

<h1>Posts</h1>
<ul>
  @foreach ($posts as $post)
    <li>{{ $post->title }}</li>
  @endforeach
</ul>

✨ 한눈에 차이점 정리

항목 Symfony Laravel

라우팅 별도 YAML or PHP 설정 routes/web.php에 직접 정의
컨트롤러 의존성 주입 강력, Response 명시적 간단하고 짧음, 유연한 코드 작성 가능
템플릿 Twig (엄격하고 안전함) Blade (Laravel 전용, 익숙한 문법)
ORM Doctrine (Entity/Repository 중심) Eloquent (Active Record 중심, 간결함)

 

Laravel과 Symfony의 4가지 주요 기능 비교를 실제 코드로 보여드릴게요:

📋 기능 목록:

  1. 📝 폼 처리 (Form Handling)
  2. 📡 API 응답 (JSON 반환)
  3. 🛠️ DB 마이그레이션
  4. ✅ 유효성 검사 (Validation)

1. 📝 폼 처리 (Form Handling)

Symfony

// src/Controller/PostController.php
use Symfony\Component\HttpFoundation\Request;
use App\Form\PostType;
use App\Entity\Post;

public function new(Request $request): Response
{
    $post = new Post();
    $form = $this->createForm(PostType::class, $post);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $entityManager->persist($post);
        $entityManager->flush();

        return $this->redirectToRoute('post_index');
    }

    return $this->render('post/new.html.twig', [
        'form' => $form->createView(),
    ]);
}
// src/Form/PostType.php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;

class PostType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('content');
    }
}

Laravel

// routes/web.php
Route::post('/posts', [PostController::class, 'store']);


// app/Http/Controllers/PostController.php
public function store(Request $request)
{
    $post = Post::create($request->only(['title', 'content']));
    return redirect('/posts');
}

// blade 파일
<form method="POST" action="/posts">
  @csrf
  <input type="text" name="title">
  <textarea name="content"></textarea>
  <button type="submit">저장</button>
</form>

2. 📡 API 응답 (JSON 반환)

Symfony

use Symfony\Component\HttpFoundation\JsonResponse;

public function apiList(): JsonResponse
{
    $posts = $postRepository->findAll();
    return $this->json($posts);
}

Laravel

public function apiList()
{
    $posts = Post::all();
    return response()->json($posts);
}

3. 🛠️ DB 마이그레이션

Symfony (Doctrine)

php bin/console make:migration
php bin/console doctrine:migrations:migrate
// src/Entity/Post.php
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */
class Post
{
    /** @ORM\Column(type="string", length=255) */
    private $title;

    /** @ORM\Column(type="text") */
    private $content;
}

Laravel (Eloquent)

php artisan make:migration create_posts_table
php artisan migrate
// database/migrations/xxxx_xx_xx_create_posts_table.php
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

4. ✅ 유효성 검사 (Validation)

Symfony

// src/Entity/Post.php
use Symfony\Component\Validator\Constraints as Assert;

class Post
{
    /**
     * @Assert\NotBlank
     * @Assert\Length(max=255)
     */
    private $title;

    /**
     * @Assert\NotBlank
     */
    private $content;
}

Laravel

public function store(Request $request)
{
    $request->validate([
        'title' => 'required|max:255',
        'content' => 'required',
    ]);

    Post::create($request->only(['title', 'content']));
}

✅ 정리

기능 Symfony Laravel

폼 처리 Form 클래스 + Twig 렌더링 Blade + POST 요청 간단 처리
API 응답 JsonResponse, ->json() response()->json()
마이그레이션 Doctrine + 콘솔 명령 artisan + Schema 빌더
유효성 검사 Entity에서 Annotation 사용 컨트롤러에서 validate() 호출

 

 

아래는 Symfony vs Laravel의 핵심 기능 비교 시리즈 2탄입니다!


이번에는 실무에서 꼭 필요한 기능인 파일 업로드, 권한 인증, 미들웨어, API Resource를 실제 코드와 함께 비교합니다.


1. 📤 파일 업로드 (File Upload)

Symfony

// 컨트롤러
public function upload(Request $request): Response
{
    $uploadedFile = $request->files->get('myfile');
    $filename = uniqid() . '.' . $uploadedFile->guessExtension();
    $uploadedFile->move($this->getParameter('upload_directory'), $filename);

    return new Response("업로드 완료: $filename");
}
<form method="POST" enctype="multipart/form-data">
    <input type="file" name="myfile">
    <button type="submit">업로드</button>
</form>

Laravel

// 컨트롤러
public function upload(Request $request)
{
    $filename = $request->file('myfile')->store('uploads');
    return response("업로드 완료: $filename");
}
<form method="POST" enctype="multipart/form-data">
    @csrf
    <input type="file" name="myfile">
    <button type="submit">업로드</button>
</form>

2. 🛡️ 권한 인증 (Authentication / Authorization)

Symfony (Security.yaml 기반)

# config/packages/security.yaml
access_control:
  - { path: ^/admin, roles: ROLE_ADMIN }
// 컨트롤러
$this->denyAccessUnlessGranted('ROLE_ADMIN');

로그인 기능은 make:auth로 scaffold 가능
사용자 권한은 Role로 구분: ROLE_USER, ROLE_ADMIN 등


Laravel (Auth Middleware)

// routes/web.php
Route::get('/admin', function () {
    // 관리자 페이지
})->middleware('auth', 'can:isAdmin');

// 사용자 모델
public function isAdmin()
{
    return $this->role === 'admin';
}
// AuthServiceProvider
Gate::define('isAdmin', function ($user) {
    return $user->role === 'admin';
});

php artisan make:auth (Laravel UI) 또는 Laravel Breeze, Jetstream 등 사용 가능


3. 🔀 미들웨어 (Middleware)

Symfony

// 이벤트 리스너 등록: services.yaml
App\EventListener\BeforeRequestListener:
    tags:
      - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

// src/EventListener/BeforeRequestListener.php
public function onKernelRequest(RequestEvent $event)
{
    $request = $event->getRequest();
    // 작업 수행
}

Laravel

// app/Http/Middleware/CheckAge.php
public function handle($request, Closure $next)
{
    if ($request->age < 18) {
        return redirect('home');
    }

    return $next($request);
}

// routes/web.php
Route::get('/restricted', function () {
    return '성인만 접근';
})->middleware('check.age');
php artisan make:middleware CheckAge

4. 📦 API Resource (데이터 정제 응답)

Symfony (Serializer Component 사용)

// 컨트롤러
use Symfony\Component\Serializer\SerializerInterface;

public function api(SerializerInterface $serializer)
{
    $posts = $this->getDoctrine()->getRepository(Post::class)->findAll();
    $json = $serializer->serialize($posts, 'json');
    return new JsonResponse($json, 200, [], true);
}

Laravel (Resource 클래스)

php artisan make:resource PostResource
// app/Http/Resources/PostResource.php
public function toArray($request)
{
    return [
        'id' => $this->id,
        'title' => $this->title,
        'summary' => Str::limit($this->content, 100),
    ];
}

// 컨트롤러
return PostResource::collection(Post::all());

✅ 정리표

기능 Symfony Laravel

파일 업로드 $request->files->get() + move $request->file()->store()
인증/권한 security.yaml + Role 기반 제어 auth, Gate, middleware, Policy
미들웨어 커널 이벤트 리스너 기반 handle() 함수로 간결한 구조
API Resource Serializer, Normalizer make:resource로 데이터 가공 간편

 

아래는 Symfony vs Laravel의 고급 기능 비교 시리즈 3탄입니다!
이번에는 실무에서 많이 사용하는 고급 백엔드 기능인 다음 4가지를 정리합니다:

RESTful API 설계
🔐 JWT 인증
🔁 Queue 작업 처리
📧 이메일 발송


1. ✅ RESTful API 설계

Symfony

컨트롤러 예:

// src/Controller/Api/PostController.php
#[Route('/api/posts', name: 'api_post_index', methods: ['GET'])]
public function index(PostRepository $repo): JsonResponse
{
    $posts = $repo->findAll();
    return $this->json($posts);
}

라우팅 방식:

  • PHP Attribute 기반 또는 config/routes.yaml 사용
  • FOSRestBundle 설치 시 더욱 RESTful하게 구조화 가능

Laravel

컨트롤러 예:

// routes/api.php
Route::apiResource('posts', PostController::class);

// PostController.php
public function index() {
    return PostResource::collection(Post::all());
}

특징:

  • apiResource() 메소드로 자동으로 RESTful 라우트 생성
  • 기본적으로 /api prefix 사용 (routes/api.php)

2. 🔐 JWT 인증

Symfony

사용 패키지:

composer require lexik/jwt-authentication-bundle

설정 파일:
config/packages/lexik_jwt_authentication.yaml

  • 비밀키 경로, TTL 등 설정
  • 로그인 시 JWT 발급

보안 설정:

firewalls:
    api:
        pattern: ^/api
        stateless: true
        jwt: ~

Laravel

사용 패키지:

composer require tymon/jwt-auth
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret

로그인 예시:

public function login(Request $request)
{
    $credentials = $request->only('email', 'password');
    if (!$token = auth()->attempt($credentials)) {
        return response()->json(['error' => 'Unauthorized'], 401);
    }
    return response()->json(['token' => $token]);
}

3. 🔁 Queue 작업 처리 (비동기 처리)

Symfony

패키지:
Symfony Messenger Component

composer require symfony/messenger

비동기 메시지 클래스 예:

// src/Message/SendEmailMessage.php
class SendEmailMessage
{
    public function __construct(private string $email) {}
}

큐 작업 등록:

$bus->dispatch(new SendEmailMessage($email));

실행:

php bin/console messenger:consume async

Laravel

큐 작업 클래스 생성:

php artisan make:job SendEmailJob

작업 정의:

public function handle()
{
    Mail::to($this->email)->send(new WelcomeMail());
}

디스패치:

SendEmailJob::dispatch($email);

실행:

php artisan queue:work

4. 📧 이메일 발송

Symfony

설정: .env에서 SMTP 정보 입력
사용 코드:

use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

$email = (new Email())
    ->from('me@example.com')
    ->to('you@example.com')
    ->subject('Hello Email')
    ->text('이메일 본문입니다');

$mailer->send($email);

Laravel

설정: .env에서 MAIL_MAILER, MAIL_HOST 등 설정
사용 코드:

Mail::to('you@example.com')->send(new WelcomeMail());

메일 클래스:

php artisan make:mail WelcomeMail
public function build()
{
    return $this->subject('환영합니다')->view('emails.welcome');
}

✅ 종합 요약표

기능 Symfony Laravel

RESTful API PHP Attribute or YAML + FOSRestBundle apiResource()로 간단하게
JWT 인증 lexik/jwt-authentication-bundle tymon/jwt-auth
Queue 작업 Messenger Component, 메시지 클래스 생성 make:job, queue:work
이메일 발송 MailerInterface + Email 클래스 Mail::to()->send(), make:mail로 구성

📌 참고 팁

  • Laravel은 간단하고 빠르게 시작하려는 프로젝트에 강점이 있고,
  • Symfony는 복잡한 비즈니스 로직, 대규모 시스템에 강력한 구조화 지원이 장점입니다.

 


RESTful API는 많은 웹 개발자들이 사용하는 개념이지만, 처음 들으면 꽤 헷갈릴 수 있습니다. 아주 쉽게 설명드릴게요:



🔍 RESTful API란?

"웹 주소(URL)와 HTTP 메서드(GET, POST 등)를 이용해서 데이터를 주고받는 규칙" 입니다.

REST는 다음의 약자입니다:

REST = REpresentational State Transfer 즉, *"자원을 표현하고 상태를 전송하는 방식"*이라는 의미입니다.



🌐 예시로 이해해보기: "게시글(Post)" 관리

자원(Resource): 게시글 (Post)

→ /posts라는 URL로 표현합니다.

동작(HTTP 메서드)에 따라 역할이 달라집니다:

메서드 URL 의미 동작 예시 GET /posts 게시글 목록 조회 리스트 보기 GET /posts/1 특정 게시글 조회 게시글 1번 상세보기 POST /posts 새 게시글 작성 글쓰기 PUT /posts/1 게시글 전체 수정 글 전체 수정 PATCH /posts/1 게시글 일부 수정 제목만 수정 등 DELETE /posts/1 게시글 삭제 글 지우기

📌 정리하면:

/posts는 자원 GET, POST, PUT, DELETE는 행동 방식 이를 결합해서 웹 API의 규칙처럼 사용하는 것이 RESTful API입니다.



💡 RESTful API의 특징

특징 설명 📍 URL로 자원 표현 /users, /products, /orders 등 🔁 HTTP 메서드 사용 GET, POST, PUT, DELETE 등 사용 💬 상태 저장 안함 (Stateless) 서버는 클라이언트의 이전 요청 상태를 기억하지 않음 📦 JSON 형식 응답 데이터는 보통 JSON으로 주고받음



🛠️ RESTful API는 왜 필요한가요?

앱, 웹, 다른 서버 등 다양한 클라이언트와 쉽게 연동 가능 표준화된 방식이므로 팀 개발, 문서화, 유지보수에 유리 프론트엔드와 백엔드를 분리한 구조에 적합 (Vue, React 등에서 자주 사용)



✅ 한줄 요약

RESTful API는 "자원 중심의 웹 통신 규칙"이며, URL + HTTP 메서드를 조합해서 데이터를 주고받는 방식입니다.



✅ 1. RESTful API vs GraphQL

비교 항목 RESTful API GraphQL 데이터 요청 방식 여러 URL 엔드포인트 단일 /graphql 엔드포인트 데이터 구조 고정된 형태로 응답 원하는 필드를 클라이언트가 직접 지정 과/소 요청 문제 필요한 필드만 못 받거나 너무 많이 받음 정확히 필요한 필드만 요청 가능 버전 관리 /v1/posts, /v2/posts 식으로 관리 버전 필요 없음 (쿼리로 유연 제어) 성능 단순 구조에 유리, 캐시 쉬움 복잡한 관계 구조에 유리 사용 예 REST API (Laravel, Django, Symfony 등 기본 구조) GitHub, Shopify 등에서 채택

🔍 예시:

REST:

GET /users/1 → { "id": 1, "name": "Kim", "email": "kim@example.com", "posts": [...] }

GraphQL:

{ user(id: 1) { name posts { title } } }



✅ 2. RESTful API 설계 예시

예: "게시글(Post)" 기능 설계

엔드포인트 예시

메서드 경로 기능 GET /api/posts 게시글 목록 조회 GET /api/posts/1 특정 게시글 조회 POST /api/posts 게시글 생성 PUT /api/posts/1 게시글 수정 DELETE /api/posts/1 게시글 삭제

응답 형식 예 (JSON)

{ "id": 1, "title": "Hello REST", "content": "RESTful API란...", "created_at": "2025-06-21" }



✅ 3. Laravel vs Symfony: RESTful API 실습 코드

🔹 Laravel 실습 코드

1) 라우팅 (routes/api.php)

Route::apiResource('posts', PostController::class);

2) 컨트롤러 (PostController.php)

public function index() { return response()->json(Post::all()); } public function store(Request $request) { $post = Post::create($request->only('title', 'content')); return response()->json($post, 201); }

3) 모델 (Post.php)

protected $fillable = ['title', 'content'];



🔹 Symfony 실습 코드

1) 라우팅 (PHP Attribute 방식)

#[Route('/api/posts', name: 'post_index', methods: ['GET'])] public function index(PostRepository $repo): JsonResponse { return $this->json($repo->findAll()); }

2) POST 저장

#[Route('/api/posts', name: 'post_store', methods: ['POST'])] public function store(Request $request, EntityManagerInterface $em): JsonResponse { $data = json_decode($request->getContent(), true); $post = new Post(); $post->setTitle($data['title']); $post->setContent($data['content']); $em->persist($post); $em->flush(); return $this->json($post, 201); }



✅ 결론 요약

항목 RESTful API GraphQL 구조 고정 유연 요청 엔드포인트별 단일 쿼리 사용성 간단한 API에 유리 복잡한 관계형 데이터에 유리

그리고 Laravel/Symfony에서는 RESTful API를 기본적으로 빠르게 구축할 수 있으며, 위의 실습 코드로 바로 API 구축이 가능합니다.


 

 

실무에서 자주 쓰이는 고급 웹 기능들에 대해 Symfony vs Laravel 기준으로 비교해 드리겠습니다.

📦 비교 항목:

  1. 🔌 WebSocket
  2. 📥 파일 다운로드
  3. 📄 PDF 생성
  4. 🔐 OAuth 연동 (소셜 로그인 등)
  5. 🧠 GraphQL API

1. 🔌 WebSocket

항목 Symfony Laravel

사용 라이브러리 Ratchet, Mercure, Swoole Laravel Echo, Pusher, BeyondCode/laravel-websockets
설정 복잡도 중~상 (Mercure 권장) 중 (패키지 설치 후 바로 사용 가능)
특징 이벤트 디스패처로 구성 가능, Mercure는 HTTP/2 기반 Laravel Echo + broadcasting 시스템으로 실시간 통신 용이
실시간 예시 채팅, 알림, 라이브 데이터 알림, 채팅, 실시간 대시보드

Laravel 코드 예시

// 이벤트 브로드캐스트
event(new \App\Events\MessageSent($message));

2. 📥 파일 다운로드

항목 Symfony Laravel

코드 예시 $response = new BinaryFileResponse($filePath); return response()->download($filePath);
경로 보호 Security 컴포넌트 설정 필요 미들웨어나 정책(Gate)으로 쉽게 설정 가능
특징 응답 헤더 수동 지정 가능 다양한 헬퍼 메서드 제공 (stream, inline 등)

3. 📄 PDF 생성

항목 Symfony Laravel

대표 패키지 KnpSnappyBundle (wkhtmltopdf 기반) barryvdh/laravel-dompdf
사용 방식 템플릿 → HTML → PDF Blade → HTML → PDF
예시 코드    
Symfony:    
$html = $twig->render('pdf/template.html.twig', [...]);
return new PdfResponse($snappy->getOutputFromHtml($html));

Laravel:

$pdf = PDF::loadView('pdf.invoice', $data);
return $pdf->download('invoice.pdf');

4. 🔐 OAuth 연동 (소셜 로그인)

항목 Symfony Laravel

대표 패키지 knpuniversity/oauth2-client-bundle Laravel Socialite
제공 플랫폼 Google, Facebook, GitHub 등 동일하게 지원
예시 코드    
Symfony:    
# security.yaml 설정 후 redirect

Laravel:

return Socialite::driver('github')->redirect();

콜백 처리:

$user = Socialite::driver('github')->user();

| 특징 | 복잡하지만 세밀한 설정 가능 | 거의 자동화 수준, 빠르게 연동 가능 |


5. 🧠 GraphQL API

항목 Symfony Laravel

대표 패키지 overblog/graphql-bundle, api-platform/graphql rebing/graphql-laravel, nuwave/lighthouse
코드 예시    
Symfony (Overblog):    
Post:
  type: object
  config:
    fields:
      id: Int
      title: String

Laravel (Lighthouse):

type Post {
  id: ID!
  title: String!
}

쿼리 예:

query {
  posts {
    id
    title
  }
}

| 특징 | 안정성과 구조화가 뛰어남 | 학습 쉬움, Laravel 생태계에 적합 |


📊 총정리 비교표

기능 Symfony Laravel

WebSocket Mercure, Ratchet (복잡하지만 강력) Echo + WebSocket 패키지로 간단히 구성
파일 다운로드 BinaryFileResponse 수동 구성 response()->download()로 바로 사용
PDF 생성 KnpSnappyBundle, 외부 라이브러리 필요 barryvdh/laravel-dompdf (Blade 연동)
OAuth 로그인 knpu/oauth2-client-bundle Laravel Socialite로 간단하고 빠름
GraphQL API Overblog, API Platform Lighthouse, Rebing 등 사용 쉬움

✅ 결론 요약

  • 빠른 개발과 쉬운 통합 → ✅ Laravel
  • 세밀한 제어, 고급 커스터마이징 → ✅ Symfony
  • GraphQL은 Laravel의 Lighthouse가 빠르게 도입 가능
  • 실시간 기능은 Laravel Echo로 구현이 쉬움

추가로 알고 싶은 정보가 있다면?

  • 웹소켓 채팅 완성 코드
  • GraphQL 실전 예제
  • PDF 생성 화면 예시

 

웹소켓 채팅 기능을 구현하는 완성 코드 예시입니다.
📦 예시는 Laravel + Laravel WebSockets + Vue.js (or JS) 기준입니다.


✅ 1. Laravel WebSocket 채팅 완성 예제


📦 패키지 설치

composer require beyondcode/laravel-websockets
php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider"
php artisan migrate

⚙️ 설정 (config/broadcasting.php)

'connections' => [
    'pusher' => [
        'driver' => 'pusher',
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'app_id' => env('PUSHER_APP_ID'),
        'options' => [
            'cluster' => 'mt1',
            'useTLS' => false,
            'host' => '127.0.0.1',
            'port' => 6001,
            'scheme' => 'http',
        ],
    ],
],

.env 예시:

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=local
PUSHER_APP_KEY=local
PUSHER_APP_SECRET=local

🧱 채널 등록 (routes/channels.php)

Broadcast::channel('chat', function ($user) {
    return ['id' => $user->id, 'name' => $user->name];
});

📣 채팅 이벤트 생성

php artisan make:event MessageSent
// app/Events/MessageSent.php
class MessageSent implements ShouldBroadcast
{
    public $user;
    public $message;

    public function __construct($user, $message)
    {
        $this->user = $user;
        $this->message = $message;
    }

    public function broadcastOn()
    {
        return new Channel('chat');
    }

    public function broadcastAs()
    {
        return 'message.sent';
    }
}

💬 컨트롤러

public function send(Request $request)
{
    $message = $request->message;
    broadcast(new MessageSent(auth()->user(), $message))->toOthers();

    return response()->json(['status' => 'Message sent']);
}

🖥️ 2. 클라이언트 (JavaScript/Vue.js)

Pusher.logToConsole = true;

window.Echo = new Echo({
    broadcaster: 'pusher',
    key: 'local',
    wsHost: window.location.hostname,
    wsPort: 6001,
    forceTLS: false,
    disableStats: true,
});

Echo.channel('chat')
    .listen('.message.sent', (e) => {
        console.log('💬 New Message:', e.user.name + ': ' + e.message);
    });
function sendMessage(e) {
    e.preventDefault();
    const message = document.getElementById('message').value;

    fetch('/send-message', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRF-TOKEN': '{{ csrf_token() }}'
        },
        body: JSON.stringify({ message })
    });
}

🚀 실행

php artisan websockets:serve
php artisan serve

✅ 결과

  • 실시간으로 모든 사용자에게 메시지 전송
  • 백엔드 이벤트 → WebSocket 브로드캐스트 → 프론트에서 자동 수신
  • 서버 내에서 동작하는 무료 Pusher 대체

 

이번엔 Symfony 기반 WebSocket 채팅 완성 예제를 제공해드리겠습니다.
두 가지 방식 중 Symfony 공식 권장 방식인 🔁 Mercure Hub 기반 실시간 채팅으로 구성합니다.


✅ Symfony + Mercure 기반 WebSocket 채팅 완성 코드


1. 🔧 Mercure 설치 및 설정

🔹 Step 1: Mercure 실행 (Docker)

docker run -d -p 3000:80 \
    -e MERCURE_PUBLISHER_JWT_KEY='!ChangeThisMercureHubJWTSecretKey!' \
    -e MERCURE_SUBSCRIBER_JWT_KEY='!ChangeThisMercureHubJWTSecretKey!' \
    dunglas/mercure

🔹 Step 2: Symfony에 Mercure 설치

composer require symfony/mercure-bundle

.env 설정:

MERCURE_PUBLISH_URL=http://localhost:3000/.well-known/mercure
MERCURE_JWT_SECRET=!ChangeThisMercureHubJWTSecretKey!

config/packages/mercure.yaml:

mercure:
    hubs:
        default:
            url: '%env(MERCURE_PUBLISH_URL)%'
            jwt: '%env(MERCURE_JWT_SECRET)%'

2. 🧱 컨트롤러 (메시지 발송)

// src/Controller/ChatController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
use Symfony\Component\Routing\Annotation\Route;

class ChatController extends AbstractController
{
    #[Route('/send-message', name: 'send_message', methods: ['POST'])]
    public function sendMessage(Request $request, HubInterface $hub)
    {
        $content = json_decode($request->getContent(), true);
        $message = $content['message'] ?? '...';

        $update = new Update(
            'https://chat.example.com/channel/general',
            json_encode(['user' => $this->getUser()?->getUserIdentifier(), 'message' => $message])
        );

        $hub->publish($update);

        return $this->json(['status' => 'Message sent']);
    }
}

3. 🌐 프론트엔드 코드 (JavaScript)

import { subscribe } from "<a href=https://unpkg.com/@mercure/client?module>https://unpkg.com/@mercure/client?module</a>";

const url = new URL("http://localhost:3000/.well-known/mercure");
url.searchParams.append("topic", "<a href=https://chat.example.com/channel/general>https://chat.example.com/channel/general</a>");

const eventSource = new EventSource(url);

eventSource.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log("📩", data.user + ": " + data.message);
};
function sendMessage(e) {
    e.preventDefault();
    const message = document.getElementById('message').value;
    fetch('/send-message', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message })
    });
}

4. ✅ 결과

  • 서버는 Mercure Hub로 메시지 전송
  • 프론트엔드는 EventSource로 실시간 수신
  • JWT 기반 구독 제어 가능 (보안 강화 가능)

🔐 참고: 사용자 인증된 JWT 토큰 생성도 가능

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Hmac\Sha256;

$token = (new Builder())
    ->withClaim('mercure', ['subscribe' => ['https://chat.example.com/channel/general']])
    ->getToken(new Sha256(), new Key('!ChangeThisMercureHubJWTSecretKey!'));

💡 요약

요소 내용

실시간 방식 Mercure (Symfony 공식 실시간 푸시 방식)
서버 Symfony + Mercure Bundle
클라이언트 EventSource (SSE 방식)
장점 쉽고 가볍고 JWT로 보안 적용 가능
대안 Ratchet 사용 시 PHP로 WebSocket 서버 직접 구성 가능 (더 복잡함)

 

아래는 Symfony + Ratchet 기반 WebSocket 채팅 완성 예제입니다.
Ratchet은 PHP로 직접 WebSocket 서버를 운영할 수 있는 라이브러리로, Mercure보다 더 저수준이며, PHP만으로 WebSocket 서버를 구성하고자 할 때 사용됩니다.


✅ Symfony + Ratchet 기반 WebSocket 채팅 완성 예제


1. 📦 Ratchet 설치

composer require cboden/ratchet

2. 🧠 WebSocket 서버 클래스 생성

// src/WebSocket/ChatServer.php
namespace App\WebSocket;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ChatServer implements MessageComponentInterface
{
    protected $clients;

    public function __construct()
    {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);
        echo "👤 연결됨: {$conn->resourceId}\n";
    }

    public function onMessage(ConnectionInterface $from, $msg)
    {
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn)
    {
        $this->clients->detach($conn);
        echo "❌ 연결 해제: {$conn->resourceId}\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "⚠️ 에러: {$e->getMessage()}\n";
        $conn->close();
    }
}

3. 🧱 WebSocket 실행 스크립트 작성

// bin/chat-server.php
use Ratchet\Http\HttpServer;
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use App\WebSocket\ChatServer;

require dirname(__DIR__) . '/vendor/autoload.php';

$chatServer = new ChatServer();

$server = IoServer::factory(
    new HttpServer(
        new WsServer($chatServer)
    ),
    8080 // 포트 설정
);

echo "✅ WebSocket 서버 실행 중: ws://localhost:8080\n";
$server->run();

4. 🌐 클라이언트 코드 (HTML + JavaScript)

<!DOCTYPE html>
<html lang="ko">
<head><meta charset="UTF-8"><title>Ratchet 채팅</title></head>
<body>
  <h2>WebSocket 채팅</h2>
  <div id="chat"></div>
  <input id="msg" type="text" />
  <button onclick="send()">보내기</button>

  <script>
    const ws = new WebSocket("ws://localhost:8080");
    const chatBox = document.getElementById("chat");

    ws.onmessage = (event) => {
      const msg = document.createElement("div");
      msg.textContent = "👤 " + event.data;
      chatBox.appendChild(msg);
    };

    function send() {
      const input = document.getElementById("msg");
      ws.send(input.value);
      input.value = '';
    }
  </script>
</body>
</html>

5. 🚀 실행 방법

php bin/chat-server.php

브라우저에서 HTML 파일을 열고 여러 창을 띄워 실시간 채팅을 테스트해보세요.


✅ 정리 요약

항목 설명

서버 구성 Ratchet (PHP WebSocket 서버) 직접 실행
프레임워크 통합 Symfony 내부 클래스 사용 가능하지만 독립 실행
포트 기본 8080 (프론트에서 ws://localhost:8080으로 연결)
사용성 가볍고 독립적이지만 보안, 인증 직접 구현 필요
확장성 채널, 사용자 식별, 인증 기능을 수동으로 구현해야 함

💬 추가 지원 가능:

  • 사용자 ID 인증/관리
  • 채널 분리 (방 나누기)
  • 데이터베이스 메시지 저장 (Doctrine)
  • WebSocket + Symfony Messenger 연동

 

 

아래는 Symfony + Ratchet 기반 WebSocket 채팅 서버를 실전용으로 확장하는 방식입니다. 


💬 Ratchet 확장 기능 통합 예제 (Symfony 기반)


✅ 1. 사용자 ID 인증/관리

목표

클라이언트가 WebSocket 연결 시 사용자 식별을 수행하여 nickname, user_id 등의 정보 포함.

구현 방법

클라이언트 → 연결 URL에 쿼리로 사용자 ID 포함 → 서버에서 검증 및 저장

const ws = new WebSocket("ws://localhost:8080?user=kim");
// onOpen
$parsedUrl = parse_url($conn->httpRequest->getUri());
parse_str($parsedUrl['query'], $queryParams);
$conn->user = $queryParams['user'] ?? 'unknown';

✅ 2. 채널 분리 (방 나누기)

목표

사용자들이 여러 채팅방(room1, room2)에 참여할 수 있도록 지원

구현

const room = 'room1';
const ws = new WebSocket(`ws://localhost:8080?room=${room}&user=kim`);
// onOpen
$conn->room = $queryParams['room'] ?? 'default';

// onMessage
foreach ($this->clients as $client) {
    if ($client->room === $from->room) {
        $client->send(json_encode(['user' => $from->user, 'msg' => $msg]));
    }
}

✅ 3. 메시지 DB 저장 (Doctrine 사용)

목표

모든 메시지를 DB에 저장하여 대화 기록 보관 가능

Entity 정의

// src/Entity/Message.php
#[ORM\Entity]
class Message
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column(type: "integer")]
    private $id;

    #[ORM\Column(type: "string")]
    private $user;

    #[ORM\Column(type: "string")]
    private $room;

    #[ORM\Column(type: "text")]
    private $content;

    #[ORM\Column(type: "datetime")]
    private $createdAt;

    public function __construct($user, $room, $content)
    {
        $this->user = $user;
        $this->room = $room;
        $this->content = $content;
        $this->createdAt = new \DateTime();
    }
}

저장 코드 (ChatServer.php)

$entityManager = $this->doctrine->getManager();
$message = new Message($from->user, $from->room, $msg);
$entityManager->persist($message);
$entityManager->flush();

주의: ChatServer에 Doctrine을 주입해야 하며 __construct()에서 받도록 수정해야 합니다.


✅ 4. Symfony Messenger 연동 (비동기 처리)

목표

메시지 저장이나 로그 기록을 메인 서버에서 분리하여 비동기 처리

구성 방법

  • MessageSavedMessage 클래스 생성 (Messenger 메시지)
  • MessageSavedHandler에서 DB 저장 처리
  • ChatServer에서는 MessageBusInterface로 디스패치
// src/Message/MessageSavedMessage.php
class MessageSavedMessage
{
    public function __construct(public string $user, public string $room, public string $content) {}
}

// src/MessageHandler/MessageSavedHandler.php
class MessageSavedHandler implements MessageHandlerInterface
{
    public function __invoke(MessageSavedMessage $message)
    {
        $msg = new Message($message->user, $message->room, $message->content);
        $this->em->persist($msg);
        $this->em->flush();
    }
}

서버에서 메시지 저장 비동기 호출

$this->bus->dispatch(new MessageSavedMessage($from->user, $from->room, $msg));

🧠 종합 흐름도

[Client]
   ↓ WebSocket 연결 (room, user 포함)
[ChatServer]
   ↳ 연결 정보 저장
   ↳ 메시지 수신 → 같은 방 사용자에게 브로드캐스트
   ↳ 메시지 저장 요청 → Messenger 메시지 디스패치
[Symfony Messenger]
   ↳ Handler에서 DB에 메시지 저장

✅ 정리 요약

기능 설명

사용자 인증 WebSocket URI 쿼리로 간단한 식별
채팅방 분리 room 필드로 다중 채널 운영 가능
DB 저장 Doctrine + Entity 구성
Messenger 연동 메시지 저장/로그 비동기 처리 가능

📁 원하시면 추가로 제공 가능:

  • 전체 프로젝트 폴더 구조 (GitHub 형식)
  • 관리자용 로그 보기 API
  • 메시지 페이징 로딩 기능 (프론트)
  • 실시간 알림 기능 확장

Symfony + Ratchet WebSocket 기반 채팅 시스템의 전체 구성도 및 확장 기능 예시를 드립니다.


📁 전체 프로젝트 폴더 구조 (GitHub 형식)

📦 my_chat_app/
├── bin/
│   └── chat-server.php         # WebSocket 실행 스크립트
├── config/
│   └── packages/
│       └── doctrine.yaml
│       └── messenger.yaml
├── public/
│   └── index.php
│   └── chat.html               # 프론트엔드 (테스트용 HTML)
├── src/
│   ├── Controller/
│   │   └── AdminLogController.php  # 관리자용 API
│   ├── Entity/
│   │   └── Message.php         # 메시지 엔티티
│   ├── Message/
│   │   └── MessageSavedMessage.php # Messenger 메시지
│   ├── MessageHandler/
│   │   └── MessageSavedHandler.php
│   ├── WebSocket/
│   │   └── ChatServer.php      # Ratchet 채팅 서버 로직
│   └── Repository/
│       └── MessageRepository.php
├── templates/
│   └── base.html.twig
├── migrations/
│   └── Version2025xxxxxx.php
├── .env
├── composer.json
└── README.md

✅ 1. 관리자용 로그 보기 API (GET /api/admin/messages)

목적: 메시지 로그를 조회 가능한 API (관리자만 접근)

// src/Controller/AdminLogController.php
#[Route('/api/admin/messages', name: 'admin_logs', methods: ['GET'])]
public function logs(MessageRepository $repo): JsonResponse
{
    $this->denyAccessUnlessGranted('ROLE_ADMIN');
    $logs = $repo->findBy([], ['createdAt' => 'DESC'], 100);

    return $this->json($logs);
}

인증 방식은 JWT 또는 세션 로그인과 연동 가능


✅ 2. 메시지 페이징 로딩 기능 (프론트 JS)

let page = 1;

function loadMessages() {
  fetch(`/api/admin/messages?page=${page}`)
    .then(res => res.json())
    .then(data => {
      data.forEach(msg => {
        const el = document.createElement('div');
        el.textContent = `[${msg.createdAt}] ${msg.user}: ${msg.content}`;
        document.getElementById('chat').prepend(el);
      });
      page++;
    });
}

// 처음 로딩 시 호출
loadMessages();

// 스크롤 상단 도달 시 이전 메시지 추가 로딩
document.getElementById('chat').addEventListener('scroll', e => {
  if (e.target.scrollTop < 10) {
    loadMessages();
  }
});

MessageRepository::getPaginated($page, $limit) 등을 구현하면 됩니다.


✅ 3. 실시간 알림 기능 확장 (접속 중 사용자 외에게 알림 전송)

목표: 메시지를 DB에 저장하는 동시에 이메일/브라우저 알림 전송

// src/MessageHandler/MessageSavedHandler.php
public function __invoke(MessageSavedMessage $msg)
{
    $message = new Message($msg->user, $msg->room, $msg->content);
    $this->em->persist($message);
    $this->em->flush();

    // 알림 예시
    $notification = new Notification(
        subject: '새 메시지 도착',
        content: "{$msg->user}님의 메시지: {$msg->content}"
    );
    $this->notifier->send($adminUser, $notification);
}

symfony/notifier 또는 이메일, Firebase 등을 통해 푸시 알림 가능


🚀 추천 구성 확장 예시

기능 구현 방법

✅ 로그인 사용자만 채팅 onOpen에서 토큰 인증
✅ 채팅방 초대/퇴장 메시지에 join/leave 타입 도입
✅ 메시지 읽음 처리 DB에 readAt 필드 추가
✅ 사용자 목록 표시 onOpen/onClose 시 사용자 리스트 브로드캐스트
✅ 방별 메시지 이력 조회 room 조건으로 MessageRepository 조회

 

✅ 1. 전체 기능 통합 ZIP 제공 (Symfony + Ratchet 기반)

  • WebSocket 채팅 서버 (ChatServer.php)
  • 채팅방 분리 (room 기반)
  • 사용자 인증/식별 (?user=kim)
  • Doctrine 메시지 저장 (Message Entity)
  • Symfony Messenger 연동 (MessageSavedHandler)
  • 관리자용 API (/api/admin/messages)
  • 메시지 페이징 (page 쿼리)
  • 실시간 알림 예시 (메일/브라우저)
  • 프론트엔드: chat.html (JS만 사용)

🔽 요청 시 ZIP 파일 다운로드 링크 제공 가능


✅ 2. GitHub Gist 기반 코드 모음 제공

  • 디렉토리 구조별 정리
  • README 포함
  • 모든 기능 포함한 ChatServer, Message, AdminController, chat.html 제공

🧩 복사해서 바로 프로젝트에 통합 가능


✅ 3. 연동 가능한 프론트엔드 프레임워크 선택

프레임워크 제공 예시

Vue.js Vue 3 + Composition API + WebSocket 실시간 채팅 컴포넌트
React React Hooks + WebSocket 기반 채팅 앱 UI
Blazor (C#) Blazor WASM 앱에서 JS interop으로 WebSocket 연결
Android (Kotlin) OkHttp WebSocket을 사용한 채팅 연결 및 RecyclerView 출력

👉 예시 요청 방식

원하시는 내용을 아래처럼 말씀해주세요:

1. ZIP 파일로 전체 예제 주세요
2. Vue.js와 연동하는 프론트 컴포넌트도 포함해주세요
3. 메시지 저장은 SQLite로 해도 됩니다

또는:

React로 사용자 목록 포함한 채팅방 UI 예제 만들어주세요
반응형