Skip to main content
Basic Svelte
Introduction
Reactivity
Props
Logic
Events
Bindings
Classes and styles
Actions
Transitions
Advanced Svelte
Advanced reactivity
Reusing content
Motion
Advanced bindings
Advanced transitions
Context API
Special elements
<script module>
Next steps
Basic SvelteKit
Introduction
Routing
Loading data
Headers and cookies
Shared modules
Forms
API routes
$app/state
Errors and redirects
Advanced SvelteKit
Hooks
Page options
Link options
Advanced routing
Advanced loading
Environment variables
Conclusion

You can also add handlers that mutate data, such as POST. In most cases, you should use form actions instead — you’ll end up writing less code, and it’ll work without JavaScript, making it more resilient.

Inside the keydown event handler of the ‘add a todo’ <input>, let’s post some data to the server:

src/routes/+page
<input
	type="text"
	autocomplete="off"
	onkeydown={async (e) => {
		if (e.key !== 'Enter') return;

		const input = e.currentTarget;
		const description = input.value;

		const response = await fetch('/todo', {
			method: 'POST',
			body: JSON.stringify({ description }),
			headers: {
				'Content-Type': 'application/json'
			}
		});

		input.value = '';
	}}
/>

Here, we’re posting some JSON to the /todo API route — using a userid from the user’s cookies — and receiving the id of the newly created todo in response.

Create the /todo route by adding a src/routes/todo/+server.js file with a POST handler that calls createTodo in src/lib/server/database.js:

src/routes/todo/+server
import { json } from '@sveltejs/kit';
import * as database from '$lib/server/database.js';

export async function POST({ request, cookies }) {
	const { description } = await request.json();

	const userid = cookies.get('userid');
	const { id } = await database.createTodo({ userid, description });

	return json({ id }, { status: 201 });
}

As with load functions and form actions, the request is a standard Request object; await request.json() returns the data that we posted from the event handler.

We’re returning a response with a 201 Created status and the id of the newly generated todo in our database. Back in the event handler, we can use this to update the page:

src/routes/+page
<input
	type="text"
	autocomplete="off"
	onkeydown={async (e) => {
		if (e.key !== 'Enter') return;

		const input = e.currentTarget;
		const description = input.value;

		const response = await fetch('/todo', {
			method: 'POST',
			body: JSON.stringify({ description }),
			headers: {
				'Content-Type': 'application/json'
			}
		});

		const { id } = await response.json();

		data.todos = [...data.todos, {
			id,
			description
		}];

		input.value = '';
	}}
/>

You should only mutate data in such a way that you’d get the same result by reloading the page.

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<script>
	let { data } = $props();
</script>
 
<div class="centered">
	<h1>todos</h1>
 
	<label>
		add a todo:
		<input
			type="text"
			autocomplete="off"
			onkeydown={async (e) => {
				if (e.key !== 'Enter') return;
 
				const input = e.currentTarget;
				const description = input.value;
				
				// TODO handle submit
 
				input.value = '';
			}}
		/>
	</label>
 
	<ul class="todos">
		{#each data.todos as todo (todo.id)}
			<li>
				<label>
					<input
						type="checkbox"
						checked={todo.done}
						onchange={async (e) => {
							const done = e.currentTarget.checked;
 
							// TODO handle change
						}}
					/>
					<span>{todo.description}</span>
					<button
						aria-label="Mark as complete"
						onclick={async (e) => {
							// TODO handle delete
						}}
					></button>
				</label>
			</li>
		{/each}
	</ul>
</div>
 
<style>
	.centered {
		max-width: 20em;
		margin: 0 auto;
	}
 
	label {
		display: flex;
		width: 100%;
	}
 
	input[type="text"] {
		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;
	}
</style>