Skip to content
Select theme

What we're building: a Markdown notes app

Step 1 of 13, and it runs nothing. This is the map: what the notes app does once the track is finished, then the twelve steps between here and there. Read it once so every later article has a place to land — each one assumes the state the previous step left behind and adds one piece to the same application.

The app is called rustnote. It is a real desktop product, not a demo: notes live in a local SQLite database, the interface is a React app rendered by the webview your operating system already ships, and everything that touches files, the database or the menu bar is Rust. That split is what Tauri is for, and it is the best way to learn how Rust and JavaScript actually talk to each other — you write both halves of the conversation.

By step 13 the app looks like this — drawn rather than photographed, because the window is the thing the last step builds:

┌─ rustnote ──────────────────────────────────────────────────────┐
│ 3 notes │ # Shopping list │ PREVIEW │
│ │ │ │
│ Shopping list │ │ Shopping list │
│ Talk ideas │ Milk, eggs, coffee. │ Milk, eggs, coffee. │
│ rustnote todo │ │ │
│ │ - [x] milk │ ☒ milk │
│ │ - [ ] coffee │ ☐ coffee │
│ │ │ │
│ + new note │ │ │
└────────────────┴──────────────────────────┴─────────────────────┘

A sidebar with every note, a Markdown editor on the left pane of the main area, the rendered preview beside it. Nothing in that window is decoration — every part of it is a step in the track:

  • The sidebar and the editor are two processes talking. React owns the window; the notes come from Rust across the IPC boundary. Step 4 builds the first command, steps 5–7 fill it with real data.
  • Autosave with no save button. Not every keystroke hits SQLite: the front end debounces, Rust writes. Step 7.
  • Search that answers as you type. SQLite’s FTS5 virtual table runs MATCH queries and hands back highlighted hits. Step 8 builds the query; step 13 puts the box in the sidebar and makes it live.
  • Importing a folder of .md files goes through Tauri’s file-system and dialog plugins — and through a permissions file you will learn to read. Step 9.
  • Closing to the tray, global shortcuts, native menus. The chrome around the window is written in Rust. Step 10.
  • Markdown rendered in Rust, not JavaScript. pulldown-cmark produces the preview and the HTML export from the same source. Step 11.
  • Signed installers for macOS, Windows and Linux. Step 12, along with the platform traps that come with them.
  • Three panes instead of one column. The window above is the last step: a grid, a preview command, and the test harness that finally goes away. Step 13.

That is the whole surface. No cloud sync, no accounts — a fast local app that owns its data and ships as a file you can double-click.

Published in this order. Each step ends at a state the next one picks up from: an app that builds, runs, and does one more thing than the step before.

# Article What lands
2 Creating a Tauri project create-tauri-app, and the three directories
3 Understanding the Tauri architecture front end, IPC, Rust core
4 Calling Rust from React with commands #[tauri::command], invoke, and the registration you forget
5 Persisting data with SQLite rusqlite, the connection, and migrations
6 Listing and reading notes a Vec<Note> across the IPC boundary
7 Editing notes and autosave debounce on the front end, update on the back
8 Full-text search with FTS5 a virtual table, MATCH, and highlighting
9 File system and drag-and-drop the plugins, and the permissions file
10 System tray, menus, and keyboard shortcuts native chrome from Rust
11 Exporting to HTML and PDF rendering Markdown in Rust, then printing
12 Building release bundles for macOS, Windows, and Linux signing, updates, and the platform traps
13 Turning the panel into the window three panes, a preview command, and the harness that goes away

The shape of it: steps 2–3 put a window on screen and explain the three layers you will be working in. Step 4 wires the two languages together — the first command, the first invoke(). Steps 5–7 are the spine: SQLite, notes as typed data, autosave. Step 8 makes search work. Steps 9–10 open the native side: files and drag-and-drop through plugins, then tray and menus. Steps 11–12 turn the app into a product people can install. Step 13 arranges all of it into the window drawn above — until then each step is exercised through a column of controls, which is all a command needs while you are still writing commands.

The Rust side of every step is written and tested against Rust 1.99.0 with edition 2024 — what a current cargo new produces. Steps 2–4 need nothing but Rust and Node on your machine; from step 5 the app carries SQLite with it, and no external database ever appears in this track.

  • Rust, installed and current. rustup.rs installs it or brings an old install up to date.
  • Node and npm. The front end is a React app, so its toolchain runs on Node. The React side stays deliberately small, but this track does not teach React or TypeScript.
  • Structs, enums, and pattern matching — note structs cross the IPC boundary, and serde carries them.
  • Option, Result, and error handling — errors have to cross that boundary too, and step 4 shows what that takes.
  • Modules, packages, and crates — a Tauri project is two codebases in one directory, and knowing which file you are editing matters from step 2 on.

No Tauri experience assumed — that is what the track is for.

Steps that change the project commit the result to tauri-desktop-app/ in the examples repository and tag it — tauri-ch02 after the setup step, and so on. If your build misbehaves partway through, checking out that tag in your clone puts your tree exactly where the article left it, and a diff against your own files shows which line drifted. This article commits nothing; the first commit lands with article 2.

Step 2 creates the project for real: create-tauri-app scaffolds the React app, the Rust crate and the configuration between them, and cargo tauri dev opens a window before you have written a line of your own code. The track overview keeps the full list current as articles land.