Laravel, CRUD 글 삭제 기능 구현

포스트 썸네일 이미지

이전 포스트에서는 글을 수정하는 기능을 구현했다.

그렇다면 당연히 오늘은? 삭제 기능을 구현할 차례다.


삭제 기능을 구현하는 것이 가장 간단하다.

만드는 것은 어렵지만, 그걸 부수는 것은 너무 간단하다는 점은 프로그래밍에서도 마찬가지인가 보다.




이전 포스트




전체적인 코드 수정은 이 깃허브 링크를 통해서 확인할 수 있다.





routes/web.php에서 라우팅 설정하기


routes/web.php의 수정 전 코드

Route::resource('posts', PostController::class)
    ->only(['index', 'store', 'edit', 'update'])
    ->middleware(['auth', 'verified']);

routes/web.php의 수정 전 코드다.

이제 여기 only(['index', 'store', 'edit', 'update']) 안에 'destroy'를 추가하면 된다.

하지만 여기서 조금 다른 코드를 사용해보려고 한다.


라라벨의 Route::resource가 기본적으로 자동 생성하는 라우트 메서드는 총 7개(index, create, store, show, edit, update, destroy)다.

그런데 그중에서 5개나 열었다.

그러면서 코드도 뭔가 길어졌다.


이쯤 되면 '그냥 only()를 삭제해서 전부 개방해 버리는 편이 낫지 않을까?'라는 의문이 생길 수도 있다.

하지만 만약 only()를 지워버리면 어떤 일이 벌어질까?


  • 보안상 문제점: 누군가 주소창에 현재도메인/posts/1 (show) 혹은 현재도메인/posts/create를 무작위로 입력했을 때, 컨트롤러에 해당 메서드가 비어있다면 런타임 에러가 발생하거나 의도치 않은 시스템 내부 정보가 노출될 수 있는 보안상 허점을 만들게 된다.
  • 실무 표준 규칙: 현업에서는 "사용하지 않는 라우트 관문은 단 하나라도 열어두지 않는다"를 철저한 보안 원칙으로 삼는다. 따라서 7개 중 5개나 열었더라도, 남은 2개의 미사용 라우트를 확실하게 차단하기 위해 only([...])를 명시하는 것이 올바른 설계다.


하지만 역시 only(['index', 'store', 'edit', 'update', 'destroy'])는 너무 길다.

이때 사용할 수 있는 게 only()와 완벽히 반대되는 역할을 하는 except() 메서드다.




routes/web.php의 수정 후 코드

Route::resource('posts', PostController::class)
    ->except(['create', 'show'])
    ->middleware(['auth', 'verified']);

이렇게 작성하면 리소스가 제공하는 총 7개의 기본 라우트 중, 배열에 담긴 create와 show 딱 2개만 제외하고, 나머지 5개(index, store, edit, update, destroy)를 자동으로 활성화해 준다.




Verb URI Action Route Name
GET /posts index posts.index
POST /posts store posts.store
GET /posts/{post}/edit edit posts.edit
PUT/PATCH /posts/{post} update posts.update
DELETE /posts/{post} destroy posts.destroy

이제 이 컨트롤러에 대한 라우팅에 destroy가 추가되었다.





index 페이지의 드롭다운 메뉴에 삭제 버튼 추가


resources/views/posts/index.blade.php에 추가할 코드

<form method="POST" action="{{ route('posts.destroy', $post) }}">
    @csrf
    @method('delete')
    <x-dropdown-link :href="route('posts.destroy', $post)" onclick="event.preventDefault(); this.closest('form').submit();">
        {{ __('Delete') }}
    </x-dropdown-link>
</form>

이 코드를 추가해야 할 위치는 Edit 버튼 바로 아래다.

깃허브 링크를 참고하자.


  • action="{{ route('posts.destroy', $post) }}": 폼 데이터가 최종적으로 전송될 목적지 주소다. 몇 번 글을 살제할 것인지 명시해야 하므로 브라우저에게 /posts/글ID 주소로 삭제 요청(Request)을 보내도록 경로를 지정해 주는 것이다.
  • @csrf: 보안 토큰을 주입하여, 이 삭제 요청이 외부 해커가 아니라 실제 내 사이트 화면에서 정상적으로 발생한 인증된 요청인지 백엔드가 검증하게 만드는 안전 장치다.
  • @method('delete'): 이전 포스트의 수정(PATCH) 기능에서 배웠던 HTTP 메서드 스푸핑Spoofing이다. 브라우저가 지원하지 못하는 DELETE 메서드를 라라벨 백엔드 커널이 오버라이딩하여 인식할 수 있도록 숨겨진 필드를 생성한다.


여기까지는 이전에 설명했던 코드이거나, 이전에 설명했던 코드에서 살짝 변형된 코드들이다.

이제부터 드롭다운 메뉴 컴포넌트인 <x-dropdown-link>에 대해서 설명하겠다.

<x-dropdown-link>는 내부적으로 <a> 태그로 렌더링되므로, 그냥 누르면 폼을 전송하는 게 아니라 주소 이동(GET 요청)을 해버리는 문제가 있다.


  • onclick="event.preventDefault();: 사용자가 버튼을 클릭했을 때, <a> 태그 본연의 기능인 '주소 이동 링크 작동'을 자바스크립트로 강제로 중단(Prevent)시킨다.
  • this.closest('form').submit();: 주소 이동을 막은 직후, 현재 나(this)를 감싸고 있는 가장 가까운 부모 태그 중 <form> 태그를 찾아서 자바스크립트로 submit() 명령을 직접 호출한다.


즉, 사용자가 Delete 메뉴를 클릭하면 자바스크립트가 링크 이동을 차단한 뒤, 뒤에 숨겨진 <form>을 대신 전송(Submit)시킨다.

최종적으로 라라벨 백엔드에 안전하게 CSRF 토큰이 포함된 DELETE 오버라이딩 요청이 도달하여 안전하게 글이 삭제되는 프론트엔드-백엔드 협업 구조다.





PostController의 destroy 메서드


PostController.php의 destroy 메서드

public function destroy(Post $post)
{
    $post->delete();

    return redirect(route('posts.index'));
}

이전 포스트의 update 메서드와 거의 같다.

다른 점은 글을 수정하는 게 아니라 ->delete() 메서드를 사용해서 글을 삭제한다는 점이다.




드롭다운 메뉴에 삭제 버튼이 추가됐다

이제 드롭다운 메뉴에 삭제 버튼도 추가되었다.

그런데 '수정'도 '삭제'도 영어로 표시되고 있다.




lang/ko.json 파일에 코드를 추가해서 한글화 시킬 수 있다.

그 코드는 여기에는 쓰지 않겠다.

깃허브 링크를 참고하자.





드롭다운 메뉴가 한글화 되었다

한글화 되었다.

그리고, 삭제 버튼을 클릭하면?




글이 성공적으로 삭제됐다

글도 성공적으로 삭제된다.




이전 포스트의 마지막에서도 언급했지만, 지금 상태로는 남이 쓴 글도 수정하거나 삭제할 수 있는 치명적인 문제가 남아있다.

다음 포스트에서는 이 문제를 해결해 보겠다.




다음 포스트

이 글이 도움이 됐거나 유익했다면 스크롤을 조금만 더 내려서 댓글을 남겨주세요. (비로그인도 가능합니다!)
응원이나 피드백이 담긴 댓글은 제가 계속 블로그를 해나갈 수 있는 원동력이 됩니다. 😊

지인에게 보여주고 싶은 글이었다면 URL을 복사해서 메신저나 소셜 미디어에 공유해 주세요.
이전 포스트 다음 포스트

댓글 쓰기

0 Comments

문의하기 양식