DEV Community

Dana Woodman
Dana Woodman

Posted on • Updated on

Getting form body data in your SvelteKit endpoints

Most of the time when working with forms, we opt to override the browser's default behavior (via <form on:submit|preventDefault={handleSubmit}>), but sometimes we just want a simple form submission.

In those cases, you may find yourself with an empty body coming into your SvelteKit endpoints. If that is you, read on!


The Problem

Suppose we have an HTML form like this and we want to POST it's content to our endpoint at /newsletter:

<form method="post" action="/newsletter">
  <input type="text" name="name" />
  <input type="email" name="email" />
  <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

If we tried to access req.body from our endpoint directly, we would get this:

ReadOnlyFormData {}
Enter fullscreen mode Exit fullscreen mode

Accessing form data

What the heck is this? With a little searching, you can see it defined in the SvelteKit types:

interface ReadOnlyFormData extends Iterator<[string, string]> {
  get: (key: string) => string;
  getAll: (key: string) => string[];
  has: (key: string) => boolean;
  entries: () => Iterator<[string, string]>;
  keys: () => Iterator<string>;
  values: () => Iterator<string>;
}
Enter fullscreen mode Exit fullscreen mode

And for the weirdos among us, you can check out the code for the ReadOnlyFormData class on Github).

But the tl;dr is that for form data, SvelteKit gives us this little class that we can use to pull out values in our endpoints.

Here is how to use the basic property accessors:

request.body.get('username')
// "sveltegroupie3000"

// Get an array of values (useful for checkboxes and selects)
request.body.getAll('favIceCreamFlavors')
// ['vanilla', 'toffee', 'caramel']

// Check if a value exists (useful for boolean checkboxes)
request.body.has('agreeToTerms')
// true
Enter fullscreen mode Exit fullscreen mode

And to get all the form data, you'll need to access the Iterators:

// Get all items in the form in an "entries" type array:
const items = [...req.body.entries()]
// [ [ "name": "Rich Harris" ], [ "hobbies", "svelte" ], [ "hobbies": "journalism" ] ]

// Get each keys:
const keys = [...req.body.keys()]
// [ "name", "hobbies", "hobbies" ]

// Get all values:
const values = [...req.body.values()]
// [ [ "Rich Harris" ], [ "svelte" ], [ "journalism" ] ]
Enter fullscreen mode Exit fullscreen mode

Now you should be able to work with your HTML form data, high five! 🙏


Going further

If you're like me, you'd rather just have a nice little object to play with of all your form data. If you want something like this, try out the following helper function to parse your form data and modify as desired:

function getFormBody(body) {
  return [...body.entries()].reduce((data, [k, v]) => {
    let value = v;
    if (value === 'true') value = true;
    if (value === 'false') value = false;
    if (k in data)
      data[k] = Array.isArray(data[k]) ? [...data[k], value] : [data[k], value];
    else data[k] = value;
    return data;
  }, {});
}


// Usage:
const body = getFormBody(req.body)
Enter fullscreen mode Exit fullscreen mode

With this you can now access your form data as you're probably use to with thinks like Express.

An additional point: this isn't the only way to submit forms in Svelte, you could also hijack the submit event and send it to an endpoint you have:

<script>
  let submit

  function handleSubmit() {
    // Send a POST request to src/routes/contact.js endpoint
    submit = fetch('/contact', {
      method: 'POST',
      body: JSON.stringify({ foo: 'bar' }),
      headers: { 'content-type': 'application/json' },
    })
      .then((resp) => resp.json())
      .finally(() => setTimeout(() => (submit = null), 5000))
  }
</script>

{#if submit}
  {#await submit}
    <p>Sending...</p>
  {:then resp}
    <p>🎉 Done!</p>
    <pre>RESPONSE: {JSON.stringify(resp, null, 2)}</pre>
  {/await}
{/if}
<form on:submit|preventDefault={handleSubmit} method="post">
  <input type="text" name="email" />
  <button type="submit">Submit</button>
</form>
Enter fullscreen mode Exit fullscreen mode

And src/routes/contact.js would look like:

export async function post(req) {
  // Simulate a delay... instead you'd do something interesting here...
  await new Promise((resolve) => setTimeout(resolve, 500))

  return { body: { success: true } }
}
Enter fullscreen mode Exit fullscreen mode

Fin

Thanks for reading and hope this was helpful! 🤓

This post was inspired by a question @Teunminator in Svelte's #svelte-kit Discord channel, thanks for a fun challenge!

PS: If you're trying to implement file uploads, you'll like see Error: File upload is not yet implemented which is because SvelteKit does not yet support it (as of this writing). You'll have to upload your files in other ways until then.

Follow me on Dev.to, Twitter and Github for more web dev and startup related content 🤓

Discussion (7)

Collapse
myleftshoe profile image
myleftshoe • Edited on

This seems to be the latest way to get form data:

export const post = async ({request}) => {
    const data = await request.formData()
    console.table([...data.entries()])
    return {
        headers: { Location: '/' },
        status: 302
    }
}

Enter fullscreen mode Exit fullscreen mode
Collapse
xenogew profile image
Natta Wang

if TypeScript, how could I specify type of 'request'?

Collapse
myleftshoe profile image
myleftshoe

Don't know tbh, I don't use type script. Since Svelte has excellent typescript support it must be in the docs somewhere. :(

Thread Thread
xenogew profile image
Natta Wang • Edited on

Thanks for your reply, it's ok, I got answer.

For those who might want to know, I'll leave it here

post({ request }: RequestEvent) {

Reason: it's destructure pattern, we need to specify type of object that will be passed to post function, and here we are looking for request property which is property of RequestEvent.

PS. RequestEvent is a type of Svelte Kit library.

Collapse
manuganji profile image
Manu Ganji

Hi, thank you for writing about this. It helped me but this was changed in this pull request.

github.com/sveltejs/kit/pull/3384

Collapse
yawnxyz profile image
Jan Z

yeah I kind of wish there was a flag you could switch in config to use the request object – it's so convenient!

Collapse
weepy profile image
weepy • Edited on

Can you just do :

const data = await request.formData()
const { username, ...} = Object.fromEntries(data)
Enter fullscreen mode Exit fullscreen mode