フォームアクション
+page.server.js ファイルは、<form> 要素を使用してサーバーにデータを POST できる *actions* をエクスポートできます。
<form> を使用する場合、クライアント側の JavaScript はオプションですが、JavaScript でフォームのインタラクションを簡単に *プログレッシブエンハンスメント* して、最高のユーザーエクスペリエンスを提供できます。
デフォルトアクション
最も簡単なケースでは、ページは default アクションを宣言します。
/** @satisfies {import('./$types').Actions} */
export const const actions: {
default: (event: any) => Promise<void>;
}
actions = {
default: (event: any) => Promise<void>default: async (event: anyevent) => {
// TODO log the user in
}
};import type { type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
default: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>default: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => {
// TODO log the user in
}
} satisfies type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;/login ページからこのアクションを呼び出すには、<form> を追加するだけです。JavaScript は必要ありません。
<form method="POST">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
</form>誰かがボタンをクリックすると、ブラウザはフォームデータを POST リクエストでサーバーに送信し、デフォルトアクションを実行します。
GETリクエストには副作用があってはならないため、アクションは常にPOSTリクエストを使用します。
action 属性を追加して、ページを指すことで、他のページからアクションを呼び出すこともできます(たとえば、ルートレイアウトのナビゲーションにログインウィジェットがある場合)。
<form method="POST" action="/login">
<!-- content -->
</form>名前付きアクション
1つの default アクションの代わりに、ページには必要なだけ名前付きアクションを含めることができます。
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: (event: any) => Promise<void>;
register: (event: any) => Promise<void>;
}
actions = {
default: async (event) => {
login: (event: any) => Promise<void>login: async (event: anyevent) => {
// TODO log the user in
},
register: (event: any) => Promise<void>register: async (event: anyevent) => {
// TODO register the user
}
};import type { type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>;
register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
default: async (event) => {
login: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>login: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => {
// TODO log the user in
},
register: (event: Kit.RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: Kit.RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
type Actions = {
[x: string]: Kit.Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;名前付きアクションを呼び出すには、名前の前に / 文字が付いたクエリパラメーターを追加します。
<form method="POST" action="?/register"><form method="POST" action="/login?/register">action 属性に加えて、ボタンで formaction 属性を使用して、親の <form> とは異なるアクションに同じフォームデータを POST することができます。
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>名前付きアクションにリダイレクトなしで POST した場合、クエリパラメーターが URL に残ってしまうため、名前付きアクションの横にデフォルトアクションを設定することはできません。つまり、次のデフォルト POST は以前の名前付きアクションを経由することになります。
アクションの構造
各アクションは RequestEvent オブジェクトを受け取り、request.formData() でデータを読み取ることができます。リクエストを処理した後(たとえば、Cookie を設定してユーザーをログインするなど)、アクションは、対応するページの form プロパティと、次の更新までアプリ全体で $page.form を通して利用できるデータで応答できます。
import * as module "$lib/server/db"db from '$lib/server/db';
/** @type {import('./$types').PageServerLoad} */
export async function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>load({ cookies: CookiesGet or set cookies related to the current request
cookies }) {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return { user: anyuser };
}
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>;
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object
request }) => {
const const data: FormDatadata = await request: RequestThe original request object
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on https://, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
};import * as module "$lib/server/db"db from '$lib/server/db';
import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad, type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = async ({ cookies: CookiesGet or set cookies related to the current request
cookies }) => {
const const user: anyuser = await module "$lib/server/db"db.getUserFromSession(cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return { user: anyuser };
};
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>;
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<{
success: boolean;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object
request }) => {
const const data: FormDatadata = await request: RequestThe original request object
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on https://, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;<script>
/** @type {{ data: import('./$types').PageData, form: import('./$types').ActionData }} */
let { data, form } = $props();
</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}<script lang="ts">
import type { PageData, ActionData } from './$types';
let { data, form }: { data: PageData, form: ActionData } = $props();
</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}レガシーモード
Svelte 4 では、プロパティを宣言するために、代わりに
export let dataとexport let formを使用していました。
検証エラー
無効なデータが原因でリクエストを処理できなかった場合、以前に送信されたフォームの値とともに検証エラーをユーザーに返すことができるため、ユーザーは再試行できます。fail 関数を使用すると、(検証エラーの場合は通常 400 または 422 の)HTTP ステータスコードをデータとともに返すことができます。ステータスコードは $page.status で、データは form で利用できます。
import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object.
fail } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
...;
}> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object
request }) => {
const const data: FormDatadata = await request: RequestThe original request object
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
if (!const email: FormDataEntryValue | nullemail) {
return fail<{
email: string | null;
missing: boolean;
}>(status: number, data: {
email: string | null;
missing: boolean;
}): ActionFailure<{
email: string | null;
missing: boolean;
}> (+1 overload)
Create an ActionFailure object.
fail(400, { email: string | nullemail, missing: booleanmissing: true });
}
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValueemail);
if (!const user: anyuser || const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)
Create an ActionFailure object.
fail(400, { email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on https://, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
};import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object.
fail } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
import type { type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
...;
}> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: string | null;
missing: boolean;
}> | ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object
request }) => {
const const data: FormDatadata = await request: RequestThe original request object
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
if (!const email: FormDataEntryValue | nullemail) {
return fail<{
email: string | null;
missing: boolean;
}>(status: number, data: {
email: string | null;
missing: boolean;
}): ActionFailure<{
email: string | null;
missing: boolean;
}> (+1 overload)
Create an ActionFailure object.
fail(400, { email: string | nullemail, missing: booleanmissing: true });
}
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValueemail);
if (!const user: anyuser || const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue;
incorrect: boolean;
}): ActionFailure<{
email: FormDataEntryValue;
incorrect: boolean;
}> (+1 overload)
Create an ActionFailure object.
fail(400, { email: FormDataEntryValueemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on https://, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;念のため、メールアドレスのみをページに返送し、パスワードは返送しないことに注意してください。
<form method="POST" action="?/login">
{#if form?.missing}<p class="error">The email field is required</p>{/if}
{#if form?.incorrect}<p class="error">Invalid credentials!</p>{/if}
<label>
Email
<input name="email" type="email" value={form?.email ?? ''}>
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>返されるデータは、JSON としてシリアル化可能である必要があります。それ以外の場合、構造は完全にあなた次第です。たとえば、ページに複数のフォームがある場合、返された form データがどの <form> を参照しているかを、id プロパティなどで区別できます。
リダイレクト
リダイレクト(およびエラー)は、load とまったく同じように機能します。
import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
/** @satisfies {import('./$types').Actions} */
export const const actions: {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object
request, url: URLThe requested URL.
url }) => {
const const data: FormDatadata = await request: RequestThe original request object
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
if (!const user: anyuser) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object.
fail(400, { email: FormDataEntryValue | nullemail, missing: booleanmissing: true });
}
if (const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object.
fail(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on https://, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
if (url: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): booleanReturns a Boolean indicating if such a search parameter exists.
has('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect(303, url: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.get(name: string): string | nullReturns the first value associated to the given search parameter.
get('redirectTo'));
}
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
};import { function fail(status: number): ActionFailure<undefined> (+1 overload)Create an ActionFailure object.
fail, function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect } from '@sveltejs/kit';
import * as module "$lib/server/db"db from '$lib/server/db';
import type { type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const actions: {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>;
register: (event: RequestEvent<...>) => Promise<...>;
}
actions = {
login: ({ cookies, request, url }: RequestEvent<Record<string, any>, string | null>) => Promise<ActionFailure<{
email: FormDataEntryValue | null;
missing: boolean;
}> | ActionFailure<...> | {
...;
}>
login: async ({ cookies: CookiesGet or set cookies related to the current request
cookies, request: RequestThe original request object
request, url: URLThe requested URL.
url }) => {
const const data: FormDatadata = await request: RequestThe original request object
request.Body.formData(): Promise<FormData>formData();
const const email: FormDataEntryValue | nullemail = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('email');
const const password: FormDataEntryValue | nullpassword = const data: FormDatadata.FormData.get(name: string): FormDataEntryValue | nullget('password');
const const user: anyuser = await module "$lib/server/db"db.getUser(const email: FormDataEntryValue | nullemail);
if (!const user: anyuser) {
return fail<{
email: FormDataEntryValue | null;
missing: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
missing: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object.
fail(400, { email: FormDataEntryValue | nullemail, missing: booleanmissing: true });
}
if (const user: anyuser.password !== module "$lib/server/db"db.hash(const password: FormDataEntryValue | nullpassword)) {
return fail<{
email: FormDataEntryValue | null;
incorrect: boolean;
}>(status: number, data: {
email: FormDataEntryValue | null;
incorrect: boolean;
}): ActionFailure<...> (+1 overload)
Create an ActionFailure object.
fail(400, { email: FormDataEntryValue | nullemail, incorrect: booleanincorrect: true });
}
cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.set(name: string, value: string, opts: CookieSerializeOptions & {
path: string;
}): void
Sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get or cookies.getAll during the current request.
The httpOnly and secure options are true by default (except on https://, where secure is false), and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option defaults to lax.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
set('sessionid', await module "$lib/server/db"db.createSession(const user: anyuser), { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
if (url: URLThe requested URL.
url.URL.searchParams: URLSearchParamssearchParams.URLSearchParams.has(name: string, value?: string): booleanReturns a Boolean indicating if such a search parameter exists.
has('redirectTo')) {
function redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | ({} & number), location: string | URL): neverRedirect a request. When called during request handling, SvelteKit will return a redirect response.
Make sure you’re not catching the thrown redirect, which would prevent SvelteKit from handling it.
redirect(303, url.searchParams.get('redirectTo'));
}
return { success: booleansuccess: true };
},
register: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>register: async (event: RequestEvent<Record<string, any>, string | null>event) => {
// TODO register the user
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;データの読み込み
アクションの実行後、ページは(リダイレクトまたは予期しないエラーが発生しない限り)再レンダリングされ、アクションの戻り値が form プロップとしてページで利用できるようになります。これは、ページが読み込まれた後、ページ内の load 関数が実行されることを意味します。
handle はアクションが呼び出される前に実行され、load 関数の前に再実行されないことに注意してください。たとえば、handle を使用して Cookie に基づいて event.locals を設定する場合、アクションで Cookie を設定または削除するときに event.locals を更新する必要があります。
/** @type {import('@sveltejs/kit').Handle} */
export async function function handle(input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}): MaybePromise<...>
handle({ event: RequestEvent<Partial<Record<string, string>>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) {
event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = await function getUser(sessionid: string | undefined): {
name: string;
}
getUser(event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>event);
}import type { type Handle = (input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle } from '@sveltejs/kit';
export const const handle: Handlehandle: type Handle = (input: {
event: RequestEvent;
resolve(event: RequestEvent, opts?: ResolveOptions): MaybePromise<Response>;
}) => MaybePromise<...>
The handle hook runs every time the SvelteKit server receives a request and
determines the response.
It receives an event object representing the request and a function called resolve, which renders the route and generates a Response.
This allows you to modify response headers or bodies, or bypass SvelteKit entirely (for implementing routes programmatically, for example).
Handle = async ({ event: RequestEvent<Partial<Record<string, string>>, string | null>event, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve }) => {
event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = await function getUser(sessionid: string | undefined): {
name: string;
}
getUser(event: RequestEvent<Partial<Record<string, string>>, string | null>event.RequestEvent<Partial<Record<string, string>>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.get(name: string, opts?: CookieParseOptions): string | undefinedGets a cookie that was previously set with cookies.set, or from the request headers.
get('sessionid'));
return resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise<Response>resolve(event: RequestEvent<Partial<Record<string, string>>, string | null>event);
};/** @type {import('./$types').PageServerLoad} */
export function function load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>): MaybePromise<void | Record<string, any>>load(event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event) {
return {
user: {
name: string;
} | null
user: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user
};
}
/** @satisfies {import('./$types').Actions} */
export const const actions: {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>logout: async (event: RequestEvent<Record<string, any>, string | null>event) => {
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.delete(name: string, opts: CookieSerializeOptions & {
path: string;
}): void
Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
delete('sessionid', { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, RouteId extends string | null = string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = null;
}
};import type { type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad, type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions } from './$types';
export const const load: PageServerLoadload: type PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>) => MaybePromise<void | Record<string, any>>PageServerLoad = (event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event) => {
return {
user: {
name: string;
} | null
user: event: ServerLoadEvent<Record<string, any>, Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user
};
};
export const const actions: {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>;
}
actions = {
logout: (event: RequestEvent<Record<string, any>, string | null>) => Promise<void>logout: async (event: RequestEvent<Record<string, any>, string | null>event) => {
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Record<string, any>, string | null>.cookies: CookiesGet or set cookies related to the current request
cookies.Cookies.delete(name: string, opts: CookieSerializeOptions & {
path: string;
}): void
Deletes a cookie by setting its value to an empty string and setting the expiry date in the past.
You must specify a path for the cookie. In most cases you should explicitly set path: '/' to make the cookie available throughout your app. You can use relative paths, or set path: '' to make the cookie only available on the current path and its children
delete('sessionid', { path: stringSpecifies the value for the
{@link
https://tools.ietf.org/html/rfc6265#section-5.2.4 Path Set-Cookie attribute
}
.
By default, the path is considered the “default path”.
path: '/' });
event: RequestEvent<Record<string, any>, string | null>event.RequestEvent<Params extends Partial<Record<string, string>> = Partial<Record<string, string>>, RouteId extends string | null = string | null>.locals: App.LocalsContains custom data that was added to the request within the server handle hook.
locals.App.Locals.user: {
name: string;
} | null
user = null;
}
} satisfies type Actions = {
[x: string]: Action<Record<string, any>, void | Record<string, any>, string | null>;
}
Actions;プログレッシブエンハンスメント
前のセクションでは、クライアント側の JavaScript なしで動作する /login アクションを構築しました。つまり、fetch はどこにもありません。それは素晴らしいことですが、JavaScript が利用可能な場合、フォームのインタラクションをプログレッシブに拡張して、より良いユーザーエクスペリエンスを提供できます。
use:enhance
フォームをプログレッシブに拡張する最も簡単な方法は、use:enhance アクションを追加することです。
<script>
import { enhance } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
</script>
<form method="POST" use:enhance><script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props();
</script>
<form method="POST" use:enhance>
use:enhanceはmethod="POST"を持つフォームでのみ使用できます。指定されたメソッドがないフォームのデフォルトであるmethod="GET"では機能しません。method="POST"を持たないフォームでuse:enhanceを使用しようとすると、エラーが発生します。
ええ、
enhanceアクションと<form action>の両方が「action」と呼ばれているのは少し紛らわしいですね。これらのドキュメントはアクション満載です。すみません。
引数なしで、use:enhance はブラウザネイティブの動作をエミュレートします。ただし、ページ全体の再読み込みは行いません。以下を行います。
- アクションが送信元のページと同じページにある場合にのみ、成功または無効な応答があったときに
formプロパティ、$page.formおよび$page.statusを更新します。たとえば、フォームが<form action="/somewhere/else" ..>のようになっている場合、formと$pageは更新されません。これは、ネイティブフォームの送信ケースでは、アクションがあるページにリダイレクトされるためです。どちらの場合も更新したい場合は、applyActionを使用してください。 <form>要素をリセットします- 成功した応答で
invalidateAllを使用してすべてのデータを無効にします - リダイレクト応答で
gotoを呼び出します - エラーが発生した場合、最も近い
+errorバウンダリをレンダリングします - 適切な要素にフォーカスをリセットします
use:enhance のカスタマイズ
動作をカスタマイズするには、フォームが送信される直前に実行される SubmitFunction を指定し、(オプションで) ActionResult で実行されるコールバックを返すことができます。コールバックを返す場合、上記のデフォルトの動作はトリガーされないことに注意してください。元に戻すには、update を呼び出します。
<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
};
}}
>これらの関数を使用して、読み込み UI などを表示および非表示にできます。
コールバックを返す場合は、デフォルトの use:enhance 動作の一部を再現する必要がある場合がありますが、成功した応答ですべてのデータを無効にする必要はありません。applyAction でそれを行うことができます
<script>
import { enhance, applyAction } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
><script lang="ts">
import { enhance, applyAction } from '$app/forms';
import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props();
</script>
<form
method="POST"
use:enhance={({ formElement, formData, action, cancel }) => {
return async ({ result }) => {
// `result` is an `ActionResult` object
if (result.type === 'redirect') {
goto(result.location);
} else {
await applyAction(result);
}
};
}}
>applyAction(result) の動作は result.type によって異なります
success、failure—$page.statusをresult.statusに設定し、(enhanceのupdateとは対照的に) 送信元に関係なく、formと$page.formをresult.dataに更新しますredirect—goto(result.location, { invalidateAll: true })を呼び出しますerror— 最も近い+errorバウンダリをresult.errorでレンダリングします
いずれの場合も、フォーカスがリセットされます。
カスタムイベントリスナー
use:enhance を使用せずに、<form> の通常のイベントリスナーを使用して、プログレッシブエンハンスメントを自身で実装することもできます。
<script>
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
/** @type {{ form: import('./$types').ActionData }} */
let { form } = $props();
/** @param {{ currentTarget: EventTarget & HTMLFormElement}} event */
async function handleSubmit(event) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
/** @type {import('@sveltejs/kit').ActionResult} */
const result = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit|preventDefault={handleSubmit}>
<!-- content -->
</form><script lang="ts">
import { invalidateAll, goto } from '$app/navigation';
import { applyAction, deserialize } from '$app/forms';
import type { ActionData } from './$types';
import type { ActionResult } from '@sveltejs/kit';
let { form }: { form: ActionData } = $props();
async function handleSubmit(event: { currentTarget: EventTarget & HTMLFormElement}) {
const data = new FormData(event.currentTarget);
const response = await fetch(event.currentTarget.action, {
method: 'POST',
body: data
});
const result: ActionResult = deserialize(await response.text());
if (result.type === 'success') {
// rerun all `load` functions, following the successful update
await invalidateAll();
}
applyAction(result);
}
</script>
<form method="POST" onsubmit|preventDefault={handleSubmit}>
<!-- content -->
</form>$app/forms の対応する方法を使用して、さらに処理を進める前に応答を deserialize する必要があることに注意してください。フォームアクションは load 関数と同様に、Date または BigInt オブジェクトを返すこともサポートしているため、JSON.parse() だけでは十分ではありません。
+page.server.js とともに +server.js がある場合、fetch リクエストはデフォルトでそこにルーティングされます。代わりに +page.server.js のアクションに POST するには、カスタムの x-sveltekit-action ヘッダーを使用します。
const const response: Responseresponse = await function fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise<Response> (+1 overload)fetch(this.action, {
RequestInit.method?: string | undefinedA string to set request’s method.
method: 'POST',
RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request’s body.
body: data,
RequestInit.headers?: HeadersInit | undefinedA Headers object, an object literal, or an array of two-item arrays to set request’s headers.
headers: {
'x-sveltekit-action': 'true'
}
});代替案
フォームアクションはサーバーにデータを送信するための推奨される方法であり、プログレッシブに拡張できますが、+server.js ファイルを使用して (たとえば) JSON API を公開することもできます。このようなインタラクションは次のようになります。
<script>
function rerun() {
fetch('/api/ci', {
method: 'POST'
});
}
</script>
<button onclick={rerun}>Rerun CI</button>/** @type {import('./$types').RequestHandler} */
export function function POST(): voidPOST() {
// do something
}import type { type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler } from './$types';
export const const POST: RequestHandlerPOST: type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
type RequestHandler = (event: Kit.RequestEvent<Record<string, any>, string | null>) => MaybePromise<Response>
RequestHandler = () => {
// do something
};GET vs POST
見てきたように、フォームアクションを呼び出すには、method="POST" を使用する必要があります。
一部のフォームでは、サーバーにデータを POST する必要はありません。たとえば、検索入力です。これらについては、method="GET" (または、同等の method なし) を使用できます。SvelteKit はそれらを <a> 要素のように扱い、ページ全体のナビゲーションではなくクライアント側のルーターを使用します。
<form action="/search">
<label>
Search
<input name="q">
</label>
</form>このフォームを送信すると、/search?q=... に移動し、ロード関数が呼び出されますが、アクションは呼び出されません。<a> 要素と同様に、ルーターの動作を制御するために、<form> に data-sveltekit-reload、data-sveltekit-replacestate、data-sveltekit-keepfocus および data-sveltekit-noscroll 属性を設定できます。