GitHub

[svelteKit] form action을 사용하는 이유

hojun lee · 02/26/2024
커버이미지

웹 애플리케이션에서 서버로 데이터를 전송하는 두가지 다른 방법

CSR 기반인 REACT를 배울 땐 당연히 fetch를 사용하여 서버와 데이터를 송수신했지만, SSR 기반으로 사용하는 svelteKit에서는 form action을 docs에 담고 있다.

왜 써야하는지 알아보자.

fetch와 form action의 차이

fetch

  1. 유연성 http 요청을 쉽게 만들고 커스텀 하기 좋다.
  2. 제어력 헤더 설정, GET/POST/PUT 등의 메소드, CORS 처리 등을 세밀하게 제어한다.
  3. 비동기처리 자바스크립트의 비동기 처리와 Promise 패턴을 사용하여 백그라운드에서 데이터를 전송하고 응답을 처리한다.
  4. 에러 핸들링이 편하다.

form action

  1. 접근성 자바스크립트가 비활성화되어 있어도 폼 액션은 작동한다.
  2. 간결성 서버사이드에서 폼 데이터를 처리할 수 있다.
  3. 자동 상태 관리 폼 제출상태를 관리 할 수 있으며, 인터페이스를 반영할 수 있다.
  4. 서버 사이드 처리 폼 데이터가 서버에서 처리되므로, 보안이 강화된다. 서버사이트에서 직접 데이터베이스와 같은 백엔드 시스템과 통신할 수 있다.
  5. 에러핸들링 서버 사이드에서 에러를 처리하고, 에러 발생 시 사용자에게 적절한 피드백을 제공한다.

sveltekit에서 core concepts 입니다만,

core

SSR 기반인 sveltekit의 app packaging을 위해서 fetch를 활용한 데이터 송수신을 하고 있었지만, 최근 내부적으로 PWA에 관심을 가지게 되어 +page.server.js를 활용한 form action을 쓰자는 이야기가 나왔다.

사실 CSR을 사용하여 fetch가 나의 데이터 송수신 기본골격으로 자리잡고 있는 상태였고, form action을 사용한다는 것은 무슨 의미인지 몰랐다.

원초적으로 웹의 기본에 가까운 form action을 사용한다는 것에 대해 알아보자

쉽게 로그인을 생각해보자

route 기반으로 데이터를 송수신하고 그 매체를 form으로 간다. html을 공부할 때 해봤을 것이다. id와 password를 넣고 버튼을 누르면 흰화면이 뿅

그렇다 form action은 refresh를 해서 새로운 데이터를 넣는다. 기존 싱글페이지애플리케이션을 사용할 때 화면이 리프레쉬되는건 죄악에 가까웠다.

SSR은 정적페이지를 생산하기 때문에 당연히 refresh가 안되면, 새로운 데이터를 가져오지 못한다는 역설이 발생한다. svelteKit은 이 것을 어떻게 해결했을까?

clent-side

고전(?)적인 방식의 form으로 서버에 POST로 submit을 한다. input에 name과 value를 담아 서버로 보내주는 방식이다. 이 때 자바스크립트의 역할은 없다.

//src/routes/login/+page.svelte
<form method="POST">
    <label>
        Email
        <input name="email" type="email">
    </label>
    <label>
        Password
        <input name="password" type="password">
    </label>
    <button>Log in</button>
</form>

server-side

request에 담긴 formData()에서 input에 binding된 값을 추출해준다. 그 중에 name으로 지정된 것들을 따로 뽑아준다.

//src/routes/login/+page.server.js

/** @type {import('./$types').Actions} */
export const actions = {
    default: async ({request}) => {
        const data = await request.formData();
        const email = data.get('email');
        const password = data.get('password');
      
      // db logic
      
      return { success: true };
    }
};

db로직까지 성실히 수행하고 우리는 return으로 session 값이나 메세지를 전달해 줄 수 있다.

다시 어떻게 받을 건데?

//src/routes/login/+page.svelte
<script>
    /** @type {import('./$types').PageData} */
    export let data;

    /** @type {import('./$types').ActionData} */
    export let form;
</script>

{#if form?.success}
    <!-- this message is ephemeral; it exists because the page was rendered in
           response to a form submission. it will vanish if the user reloads -->
    <p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}

export let form 을 통해 해당 메세지가 내려온다. 이는 보다시피 순차적인 방법으로 모든 게 진행된다. 다시 시작된 내리사랑

client-side Javascript 사용 없이 진행되지만, 뭔가 불편한 기분은 지울 수 없다.

리프레쉬로 인해 변수에 저장된 데이터들은 모두 사라진 상태 기존에는 요리조리 데이터를 관리해왔는데, 이젠 어떻게 데이터를 관리할지 조금 막막해졌다.

svelte의 최대 장점인 양방향 binding이 무의미해진 느낌이랄까.

Progressive enhancement

강화!!!!!! form action을 강화한다.

src/routes/login/+page.svelte
<script>
    import { enhance } from '$app/forms';

    /** @type {import('./$types').ActionData} */
    export let form;
</script>

<form method="POST" use:enhance>

use:enhance를 사용하면 full-page reloads 즉, refresh가 되지 않는다.

뭔가 reactive한 느낌이 살아난다. javascript가 안돌아는 환경을 고려해야 하는 서비스가 몇개나 될까.

우리는 enhance를 사용해 구현하기로 했다.

Customising use:enhance

그래도 문제가 많다. call 함수에 너무많은 전처리와 시한폭탄들을 심어뒀나보다.

우리는 기존 fetch를 사용할 때 처럼 전처리와 돌아와서 처리하는 비동기를 만들어줄 수 있는데,

<form
    method="POST"
    use:enhance={({ formElement, formData, action, cancel, submitter }) => {
        // `formElement` is this `<form>` element
        // `formData` is its `FormData` object that's about to be submitted
        // `action` is the URL to which the form is posted
        // calling `cancel()` will prevent the submission
        // `submitter` is the `HTMLElement` that caused the form to be submitted

        return async ({ result, update }) => {
            // `result` is an `ActionResult` object
            // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
        };
    }}
>

use:enhance에 함수를 매달아주면 된다. 파라미터는 확인해보면 된다. 이것까지 쓰면 자바스크립트가 없는 환경에선 또 안돌아 갈 것 같긴하다.

그 것은 자신의 개발환경과 실행환경에 맞게 알아서 구축하자.

결론

나는 SSR를 주먹구구로 사용하면서 refetch에 큰 문제를 만난 적이 있다. 결국 구축환경에 대한 이해가 적었고, 작은 범위 내에서의 지식만을 활용했다. 중요한 건 데이터의 흐름을 이해하는 것이다.

기본을 잘 알아야 좋은 개발자가 될 것이다.

차근차근 잘 알아보고 적용해보자.