Les fonctions distantes (_remote functions_) sont un outil pour communiquer de manière typée entre
le client et le serveur. Elles peuvent être appelées depuis _n'importe où_ dans votre application,
mais sont _toujours exécutées_ sur le serveur, ce qui signifie qu'elles peuvent accéder de manière
sécurisée aux [modules réservés au serveur](server-only-modules) contenant des choses comme des
variables d'environnement ou des clients de base de données.
Combinées avec le support expérimental de Svelte pour [`await`](/docs/svelte/await-expressions),
elles vous permettent de charger et de manipuler les données directement au sein de vos composants.
Cette fonctionnalité est actuellement expérimentale, ce qui signifie qu'elle contient très
certainement des bugs, et peut être modifiée à tout moment. Vous devez l'activer en ajouter l'option
`kit.experimental.remoteFunctions` dans votre fichier `svelte.config.js` :
```js
/// file: svelte.config.js
export default {
kit: {
experimental: {
+++remoteFunctions: true+++
}
}
};
```
## Aperçu [!VO]Overview
Les fonctions distantes (_remote_) sont exportées depuis un fichier `.remote.js` ou `.remote.ts`, et
sont disponibles en quatre versions : `query`, `form`, `command`, et `prerender`. Sur le client, les
fonctions exportées sont transformées en wrappers autour de `fetch` qui invoquent leur contrepartie
sur le serveur en utilisant un endpoint HTTP généré. Les fichiers `.remote.` doivent être placés
dans les dossiers `lib` ou `routes`.
## query
La fonction `query` vous permet de lire des données dynamiques depuis le serveur (pour les données
_statiques_, envisagez plutôt l'utilisation de [`prerender`](#prerender)) :
```js
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
// @filename: index.js
// ---cut---
import { query } from '$app/server';
import * as db from '$lib/server/database';
export const getPosts = query(async () => {
const posts = await db.sql`
SELECT title, slug
FROM post
ORDER BY published_at
DESC
`;
return posts;
});
```
> [!NOTE] Tout au long de cette page, vous verrez des imports depuis des modules fictifs comme
> `$lib/server/database` et `$lib/server/auth`. Ces imports n'existent que pour illustrer les propos
> — vous pouvez bien sûr utiliser n'importe quel client de base de données et n'importe quel système
> d'authentification.
>
> La fonction `db.sql` ci-dessus est une [fonction de gabarit
> étiquété](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Template_literals#gabarits_%C3%A9tiquet%C3%A9s)
> qui échappe toute valeur interpolée.
La query renvoyée par `getPosts` fonctionne comme une
[`Promise`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise)
qui résout des `posts` :
```svelte
```
Tant que la promesse n'est pas résolue — et si elle échoue — la
[``](../svelte/svelte-boundary) la plus proche sera invoquée.
Bien que l'utilisation de `await` soit recommandée, vous pouvez également utiliser en alternative
les propriétés `loading`, `error` et `current` de la query :
```svelte
{#if query.error}
{/if}
```
> [!NOTE] Pour le reste de ce document, nous utiliserons la forme `await`.
### Arguments de query [!VO]Query arguments
Les fonctions `query` peuvent accepter un argument, comme le `slug` d'un article individuel :
```svelte
{post.title}
{@html post.content}
```
Puisque `getPost` expose un endpoint HTTP, il est important de valider cet argument pour garantir
qu'il est du type attendu. Pour cela, nous pouvons utiliser n'importe quelle librairie de validation
de [Standard Schema](https://standardschema.dev/), comme [Zod](https://zod.dev/) ou
[Valibot](https://valibot.dev/) :
```js
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
// @filename: index.js
// ---cut---
import * as v from 'valibot';
import { error } from '@sveltejs/kit';
import { query } from '$app/server';
import * as db from '$lib/server/database';
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => {
const [post] = await db.sql`
SELECT * FROM post
WHERE slug = ${slug}
`;
if (!post) error(404, 'Not found');
return post;
});
```
L'argument et la valeur de retour sont tous deux sérialisés avec
[devalue](https://github.com/sveltejs/devalue), qui gère des types comme `Date` et `Map` (et
également des types personnalisés que vous pouvez définir dans votre [transport
hook](hooks#Universal-hooks-transport)) en plus du JSON.
### Mettre les queries à jour [!VO]Refreshing queries
Toute query peut être mise à jour via sa méthode `refresh` :
```svelte
```
> [!NOTE] Les queries sont mises en cache tant qu'elles sont sur la page, ce qui veut dire que
> `getPosts() === getPosts()`. Cela signifie que vous n'avez pas besoin d'une référence comme `const
> posts = getPosts()` pour mettre à jour la query.
## form
La fonction `form` facilite l'écriture de données sur le serveur. Elle prend un callback qui reçoit
le [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) actuel...
```ts
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
declare module '$lib/server/auth' {
interface User {
name: string;
}
/**
* Récupère les informations de l'utilisateur à partir des cookies, en utilisant `getRequestEvent`
*/
export function getUser(): Promise;
}
// @filename: index.js
// ---cut---
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { query, form } from '$app/server';
import * as db from '$lib/server/database';
import * as auth from '$lib/server/auth';
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => { /* ... */ });
export const createPost = form(async (data) => {
// Vérifie si l'utilisateur est connecté
const user = await auth.getUser();
if (!user) error(401, 'Unauthorized');
const title = data.get('title');
const content = data.get('content');
// Vérifie si les données sont valides
if (typeof title !== 'string' || typeof content !== 'string') {
error(400, 'Title and content are required');
}
const slug = title.toLowerCase().replace(/ /g, '-');
// Insère en base de données
await db.sql`
INSERT INTO post (slug, title, content)
VALUES (${slug}, ${title}, ${content})
`;
// Redirige vers la page nouvellement créée
redirect(303, `/blog/${slug}`);
});
```
... et renvoie un objet qui peut être distribué sur un élément `
```
L'objet de formulaire contient les propriétés `method` et `action` qui lui permettent de fonctionner
sans JavaScript (c-à-d qu'il enverra les données puis qu'il rechargera la page). Il a également un
gestionnaire `onsubmit` qui améliore progressivement le formulaire lorsque JavaScript est
disponible, en soumettant les données *sans* recharger entièrement la page.
### Mutations single-flight [!VO]Single-flight mutations
Par défaut, toutes les queries utilisées sur la page (en plus de toutes fonction `load`) sont
automatiquement mises à jour à la suite d'une soumission de formulaire. Ceci assure que tout soit à
jour, mais est également inefficace : beaucoup de queries seront inchangées, et cela requiert un
deuxième aller-retour vers le serveur pour obtenir les données à jour.
À la place, nous pouvons préciser quelles queries devraient être mises à jour en réponse à une
soumission particulière de formulaire. On appelle cela une _mutation single-flight_ (mutation sur un
seul aller-retour), et il y a deux manières de faire. La première est de rafraîchir la query sur le
serveur, dans le gestionnaire de formulaire :
```js
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { query, form } from '$app/server';
const slug = '';
// ---cut---
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => { /* ... */ });
export const createPost = form(async (data) => {
// la logique du formulaire se place ici...
// Met à jour `getPosts()` sur le serveur, et
// renvoie les données au client avec le résultat
// de `createPost`
+++await getPosts().refresh();+++
// Redirige vers la page nouvellement créée
redirect(303, `/blog/${slug}`);
});
```
La seconde est de conduire la mutation single-flight depuis le client, ce que nous verrons dans la
section sur [`enhance`](#form-enhance).
### Renvois et redirections [!VO]Returns and redirects
L'exemple ci-dessus utilise [`redirect(...)`](@sveltejs-kit#redirect), qui redirige l'utilisateur ou
l'utilisatrice vers la page nouvellement créée. De manière alternative, le callback pourrait
également renvoyer les données, auquel cas elles seraient disponibles en tant que
`createPost.result` :
```ts
/// file: src/routes/blog/data.remote.js
// @filename: ambient.d.ts
declare module '$lib/server/database' {
export function sql(strings: TemplateStringsArray, ...values: any[]): Promise;
}
declare module '$lib/server/auth' {
interface User {
name: string;
}
/**
* Récupère les informations de l'utilisateur à partir des cookies, en utilisant `getRequestEvent`
*/
export function getUser(): Promise;
}
// @filename: index.js
import * as v from 'valibot';
import { error, redirect } from '@sveltejs/kit';
import { query, form } from '$app/server';
import * as db from '$lib/server/database';
import * as auth from '$lib/server/auth';
export const getPosts = query(async () => { /* ... */ });
export const getPost = query(v.string(), async (slug) => { /* ... */ });
// ---cut---
export const createPost = form(async (data) => {
// ...
return { success: true };
});
```
```svelte
Créer un nouvel article
{#if createPost.result?.success}
Publié avec succès !
{/if}
```
Cette valeur est _éphémère_ — elle disparaîtra si vous re-soumettez le formulaire, si vous naviguez
sur une autre page, ou si vous rechargez la page.
> [!NOTE] La valeur de `result` n'indique pas nécessairement un succès — elle peut également
> contenir des erreurs de validation, ainsi que des données permettant de re-remplir le formulaire
> en cas de rechargement de la page.
Si une erreur se produit lors de la soumission, la page `+error.svelte` la plus proche sera
affichée.
### enhance
Nous pouvons personnaliser ce qui se produit lorsque le formulaire est soumis grâce à la méthode
`enhance` :
```svelte
Créer un nouvel article
```
Le callback reçoit l'élément `form`, la `data` qu'il contient, et une fonction `submit`.
Pour activer les [mutations single-flight](#form-Single-flight-mutations) depuis le client, utilisez
`submit().updates(...})`. Par exemple, si la query `getPosts()` était utilisée sur cette page, nous
pourrions la mettre à jour de cette manière :
```ts
import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit';
interface Post {}
declare function submit(): Promise & {
updates(...queries: Array | RemoteQueryOverride>): Promise;
}
declare function getPosts(): RemoteQuery;
// ---cut---
await submit().updates(getPosts());
```
Nous pouvons aussi _surcharger_ les données courantes pendant que la soumission est en cours de
traitement :
```ts
import type { RemoteQuery, RemoteQueryOverride } from '@sveltejs/kit';
interface Post {}
declare function submit(): Promise & {
updates(...queries: Array | RemoteQueryOverride>): Promise;
}
declare function getPosts(): RemoteQuery;
declare const newPost: Post;
// ---cut---
await submit().updates(
getPosts().withOverride((posts) => [newPost, ...posts])
);
```
La surcharge sera appliquée immédiatement, et annulée lorsque la soumission se termine (ou échoue).
### buttonProps
Par défaut, soumettre un formulaire va envoyer une requête à l'URL indiquée par l'attribut
[`action`](https://developer.mozilla.org/fr/docs/Web/HTML/Reference/Elements/form#attributs_pour_lenvoi_de_formulaires)
de l'élément `