Skip to content
Learni
Voir tous les tutoriels
Développement Desktop

Comment créer une app desktop avec Tauri en 2026

Read in English

Introduction

Tauri est un framework open-source révolutionnaire pour développer des applications desktop cross-platform (Windows, macOS, Linux) en combinant des technologies web frontend (HTML, CSS, JS/TS) avec un backend Rust ultra-performant et sécurisé. Contrairement à Electron qui embarque Chromium (150+ Mo par app), Tauri utilise le WebView natif du système (10-20 Mo), réduisant drastiquement la taille et la consommation mémoire. En 2026, avec Tauri v2, les capabilities permettent un contrôle granulaire des permissions, évitant les failles de sécurité courantes. Ce tutoriel expert vous guide pas à pas pour créer une app complète : un gestionnaire de tâches avec persistance fichier, appels Rust asynchrones et build optimisé. Parfait pour les devs seniors cherchant scalabilité, sécurité zero-trust et performances natives. À la fin, votre app sera production-ready, signable et distributable via AppImage/MSI/DMG. (142 mots)

Prérequis

  • Rust : Version stable (1.80+), installez via rustup.rs
  • Node.js : 20+ avec npm/yarn/pnpm
  • Système de build : Visual Studio (Windows), Xcode (macOS), ou clang/gcc (Linux)
  • Outils optionnels : Git, un éditeur comme VS Code avec extensions Rust/Tauri
  • Connaissances : Rust intermédiaire, TypeScript, Web APIs (Fetch, DOM)

Installer Tauri CLI

terminal
cargo install tauri-cli --version "^2.0"
npm install -g @tauri-apps/cli

tauri --version
tauri doctor

Cette commande installe la CLI Tauri v2 via Cargo pour les outils Rust, et npm pour les wrappers JS. tauri doctor vérifie l'environnement (WebView2 sur Windows, etc.). Évitez les versions git pour la stabilité en prod ; mettez à jour avec cargo update si besoin.

Vérification et première commande

Exécutez tauri doctor pour valider les dépendances système. Sur Windows, installez WebView2 Runtime si absent. Analogie : comme un check-up avant chirurgie, cela prévient 90% des builds foireux. Passez ensuite à la création du projet avec un template Vite + Vanilla pour simplicité experte.

Créer le projet Tauri

terminal
npm create tauri-app@latest mon-app-tauri
cd mon-app-tauri
npm install
npm run tauri dev

Crée un projet boilerplate avec Vite frontend et Tauri backend. npm run tauri dev lance le hot-reload : Rust compile en ~2s, WebView s'ouvre. Personnalisez le template (React/Svelte) via prompts. Piège : sur macOS, accordez permissions Developer Tools dans Sécurité.

Structure du projet

Le dossier src-tauri contient le backend Rust (Cargo.toml, main.rs, tauri.conf.json). src gère le frontend web. Les capabilities (v2) séparent permissions en fichiers JSON isolés, comme des rôles RBAC. Modifiez src-tauri/capabilities/default.json pour exposer des APIs sans allowlist obsolète.

Configurer tauri.conf.json

src-tauri/tauri.conf.json
{
  "productName": "MonAppTauri",
  "version": "1.0.0",
  "identifier": "com.exemple.monapp",
  "build": {
    "devPath": "../src",
    "distDir": "../dist"
  },
  "app": {
    "windows": [{
      "title": "Gestionnaire Tâches",
      "width": 800,
      "height": 600,
      "resizable": true
    }],
    "security": {
      "csp": null
    }
  },
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ]
  }
}

Configure nom, fenêtres, bundle multiplateforme et icônes (pré-générez-les). identifier doit être unique (reverse-domain). En v2, CSP null autorise eval() pour DX ; en prod, raffermissez-le. Piège : oubliez distDir et Vite ne build pas.

Définir les capabilities

src-tauri/capabilities/main.json
{
  "$schema": "https://schema.tauri.app/config/2.0.0/capability",
  "identifier": "main-capability",
  "description": "Capabilities principales",
  "local": true,
  "windows": ["main"],
  "permissions": [
    "core:default",
    {
      "identifier": "fs:allow-read",
      "allow": [
        { "path": "$APPDATA/../local/tasks.json" }
      ]
    },
    {
      "identifier": "shell:allow-execute",
      "allow": [{
        "name": "ls",
        "args": true,
        "sidecar": false
      }]
    }
  ]
}

Définit permissions granulaires : lecture JSON dans AppData, exécution shell. $APPDATA est résolu nativement. Associez à tauri.conf.json via capabilities: ["main-capability"]. Avantage v2 : isolation fenêtres, audits sécurisés.

Implémenter le backend Rust

Créez des commandes invocables depuis JS via invoke. Utilisez serde pour JSON, std::fs pour persistance. Analogie : Rust comme un garde du corps – vérifie tout avant d'accéder au DOM.

Commandes Rust (main.rs)

src-tauri/src/main.rs
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

use serde::{Deserialize, Serialize};
use std::fs;
use tauri::{command, Manager};

#[derive(Serialize, Deserialize)]
struct Task {
    id: u32,
    title: String,
    done: bool,
}

type Tasks = Vec<Task>;

const TASKS_FILE: &str = "tasks.json";

#[command]
fn get_tasks() -> Result<Tasks, String> {
    let path = dirs::data_local_dir()
        .ok_or("Impossible de trouver data dir")?
        .join(TASKS_FILE);
    if !path.exists() {
        return Ok(vec![]);
    }
    let data = fs::read_to_string(&path).map_err(|e| e.to_string())?;
    let tasks: Tasks = serde_json::from_str(&data).unwrap_or(vec![]);
    Ok(tasks)
}

#[command]
fn add_task(title: String) -> Result<Tasks, String> {
    let mut tasks = get_tasks().unwrap_or(vec![]);
    let new_id = tasks.len() as u32 + 1;
    tasks.push(Task { id: new_id, title, done: false });
    let path = dirs::data_local_dir().unwrap().join(TASKS_FILE);
    fs::write(&path, serde_json::to_string_pretty(&tasks).unwrap()).map_err(|e| e.to_string())?;
    Ok(tasks)
}

#[command]
fn toggle_task(id: u32) -> Result<Tasks, String> {
    let mut tasks = get_tasks().map_err(|e| e)?;
    if let Some(task) = tasks.iter_mut().find(|t| t.id == id) {
        task.done = !task.done;
        let path = dirs::data_local_dir().unwrap().join(TASKS_FILE);
        fs::write(&path, serde_json::to_string_pretty(&tasks).unwrap()).map_err(|e| e.to_string())?;
    }
    Ok(tasks)
}

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .invoke_handler(tauri::generate_handler![get_tasks, add_task, toggle_task])
        .run(tauri::generate_context!())
        .expect("Erreur lors du run Tauri");
}

Implémente CRUD tâches avec persistance JSON locale. dirs résout chemins OS-safe. Ajoutez tauri-plugin-fs dans Cargo.toml. Erreur courante : oublier windows_subsystem (no console Windows). Sécurisé via capabilities.

Frontend TypeScript (main.tsx)

src/main.tsx
import { createRoot } from 'react-dom/client';
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
import { listen } from '@tauri-apps/api/event';

type Task = {
  id: number;
  title: string;
  done: boolean;
};

const App: React.FC = () => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [newTask, setNewTask] = useState('');

  const loadTasks = async () => {
    try {
      const loaded: Task[] = await invoke('get_tasks');
      setTasks(loaded);
    } catch (e) {
      console.error(e);
    }
  };

  useEffect(() => {
    loadTasks();
  }, []);

  const addTask = async () => {
    if (!newTask.trim()) return;
    try {
      const updated: Task[] = await invoke('add_task', { title: newTask });
      setTasks(updated);
      setNewTask('');
    } catch (e) {
      console.error(e);
    }
  };

  const toggleTask = async (id: number) => {
    try {
      const updated: Task[] = await invoke('toggle_task', { id });
      setTasks(updated);
    } catch (e) {
      console.error(e);
    }
  };

  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      <h1>Gestionnaire de Tâches Tauri</h1>
      <div>
        <input
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
          placeholder="Nouvelle tâche"
          style={{ marginRight: '10px', padding: '8px' }}
        />
        <button onClick={addTask}>Ajouter</button>
      </div>
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {tasks.map((task) => (
          <li key={task.id} style={{ margin: '10px 0' }}>
            <input
              type="checkbox"
              checked={task.done}
              onChange={() => toggleTask(task.id)}
            />
            <span style={{ textDecoration: task.done ? 'line-through' : 'none', marginLeft: '10px' }}>
              {task.title}
            </span>
          </li>
        ))}
      </ul>
    </div>
  );
};

const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(<App />);

Frontend React simple avec invoke pour appeler Rust. Utilisez @tauri-apps/api. Hot-reload via Vite. Ajoutez npm i react react-dom @types/react @types/react-dom @tauri-apps/api. Piège : async sans try/catch crash l'app.

Mettre à jour Cargo.toml

src-tauri/Cargo.toml
[package]
name = "mon-app-tauri"
version = "1.0.0"
edition = "2021"

[build-dependencies]
 tauri-build = { version = "2.0", features = [] }

[dependencies]
 tauri = { version = "2.0", features = [ "shell-open", "protocol-asset" ] }
 tauri-plugin-fs = "2.0"
 serde = { version = "1.0", features = [ "derive" ] }
 serde_json = "1.0"
 tokio = { version = "1", features = [ "full" ] }
 dirs = "5.0"

Dépendances pour Tauri v2, FS plugin, JSON, Tokio async. tauri-build pour context. edition 2021 pour features modernes. Exécutez cargo check après édition.

Build pour production

terminal
npm run tauri build
# Ou pour un target spécifique
npm run tauri build -- --target x86_64-apple-darwin
# Signer sur macOS (exemple)
security find-identity -v -p codesigning

Génère bundles natifs (EXE/DMG/AppImage). --target pour cross-compile (installez toolchains via rustup). Sur macOS/Windows, signez pour Store. Taille finale ~15 Mo vs 200+ Electron.

Bonnes pratiques

  • Capabilities first : Toujours isoler permissions par fenêtre/role ; auditez avec tauri audit.
  • Rust pour core logic : Déplacez calculs lourds (crypto, DB) en natif ; JS pour UI seulement.
  • Plugins officiels : Utilisez sql pour SQLite, store pour key-value au lieu de fs raw.
  • Tests end-to-end : cargo test + Playwright pour UI ; CI avec GitHub Actions.
  • Updater : Intégrez tauri-plugin-updater pour auto-updates via GitHub Releases.

Erreurs courantes à éviter

  • Permissions manquantes : invoke échoue silencieusement ; vérifiez logs Rust avec RUST_LOG=debug npm run tauri dev.
  • Chemins absolus : Utilisez toujours $APPDATA/$HOME ; dirs crate pour portabilité.
  • Hot-reload lent : Augmentez devServer timeout dans tauri.conf ; évitez watch sur node_modules.
  • Build cross-plat : Installez targets Rust (rustup target add) ; testez sur VM avant merge.

Pour aller plus loin

Plongez dans les docs Tauri v2, explorez plugins comme tauri-plugin-sql pour DB embarquée ou tauri-plugin-global-shortcut. Pour maîtriser Rust/Tauri en profondeur, découvrez nos formations Learni certifiantes. Contribuez sur GitHub pour plugins custom.