Skip to main content

In the chapter on loading data, we saw how to get data from the server to the browser. Sometimes you need to send data in the opposite direction, and that’s where <form> — the web platform’s way of submitting data — comes in.

Let’s build a todo app. We’ve already got an in-memory database set up in src/lib/server/database.js, and our load function in src/routes/+page.server.js uses the cookies API so that we can have a per-user todo list, but we need to add a <form> to create new todos:

src/routes/+page
<h1>todos</h1>

<form method="POST">
	<label>
		add a todo:
		<input
			name="description"
			autocomplete="off"
		/>
	</label>
</form>

<ul class="todos">

If we type something into the <input> and hit Enter, the browser makes a POST request (because of the method="POST" attribute) to the current page. But that results in an error, because we haven’t created a server-side action to handle the POST request. Let’s do that now:

src/routes/+page.server
import * as db from '$lib/server/database.js';

export function load({ cookies }) {
	// ...
}

export const actions = {
	default: async ({ cookies, request }) => {
		const data = await request.formData();
		db.createTodo(cookies.get('userid'), data.get('description'));
	}
};

The request is a standard Request object; await request.formData() returns a FormData instance.

When we hit Enter, the database is updated and the page reloads with the new data.

Notice that we haven’t had to write any fetch code or anything like that — data updates automatically. And because we’re using a <form> element, this app would work even if JavaScript was disabled or unavailable.

Edit this page on GitHub

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<script>
	let { data } = $props();
</script>
 
<div class="centered">
	<h1>todos</h1>
 
	<ul class="todos">
		{#each data.todos as todo (todo.id)}
			<li>
				{todo.description}
			</li>
		{/each}
	</ul>
</div>
 
<style>
	.centered {
		max-width: 20em;
		margin: 0 auto;
	}
 
	label {
		width: 100%;
	}
 
	input {
		flex: 1;
	}
 
	span {
		flex: 1;
	}
 
	button {
		border: none;
		background: url(./remove.svg) no-repeat 50% 50%;
		background-size: 1rem 1rem;
		cursor: pointer;
		height: 100%;
		aspect-ratio: 1;
		opacity: 0.5;
		transition: opacity 0.2s;
	}
 
	button:hover {
		opacity: 1;
	}
 
	.saving {
		opacity: 0.5;
	}
</style>