- Rust 61.3%
- Svelte 30.3%
- TypeScript 3.8%
- Nix 2.5%
- CSS 0.9%
- Other 1.2%
Backend: - UpdateTaskRequest gains group_id (double-option: absent=no change, null=personal, number=group); resets column_id on list move Frontend: - Task modal: List dropdown to move task to any group or Personal; clears assignee on change; removed task disappears from current view - Sidebar: Manage columns button appears in kanban view (fixes personal board having no way to add/edit columns) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| be | ||
| frontend | ||
| nix | ||
| .gitignore | ||
| docker-compose.yml | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
Tasks
Home organisation task app for two people. SvelteKit PWA frontend embedded into a Rust/Rocket backend, deployed as a single binary.
Stack
- Backend — Rust, Rocket, SeaORM, SQLite, JWT auth
- Frontend — SvelteKit, TypeScript, PWA (offline-capable, installable)
- Build — Nix flake; frontend is compiled and embedded into the binary via
rust-embed
Development
Enter the dev shell (provides Rust stable + cargo-watch + Node 22):
nix develop
Run backend and frontend dev servers in separate terminals:
# terminal 1 — backend (auto-reloads on save)
cd be
DATABASE_URL=sqlite://./dev.db cargo watch -x run
# terminal 2 — frontend dev server (proxies /api and /auth to :8000)
cd frontend
npm run dev
The frontend dev server runs on http://localhost:5173. Configure a proxy in vite.config.js if you need the frontend to talk to the backend during development.
Nix build
# build the backend binary (with frontend embedded)
nix build
# build only the frontend
nix build .#frontend
# build only the backend
nix build .#backend
The resulting binary at ./result/bin/tasks-be serves everything: the REST API and the compiled frontend as static files.
Which URL serves the frontend?
The frontend and API share the same port. The backend serves the compiled SvelteKit app at / — just open http://host:port/ in a browser. Routes are prioritised as follows:
| Path | Served by |
|---|---|
/api/* |
REST API |
/auth/*, /healthz, /readyz |
Backend routes |
/swagger-ui/, /rapidoc/ |
API docs |
/metrics |
Prometheus |
/* (everything else) |
SvelteKit frontend (falls back to index.html for client-side routing) |
First-time setup
The database starts empty. Registration is open and requires no authentication, so create both accounts before exposing the service publicly.
curl -s -X POST https://tasks.recfx.net/auth/register \
-H 'Content-Type: application/json' \
-d '{"username":"alice","password":"..."}'
curl -s -X POST https://tasks.recfx.net/auth/register \
-H 'Content-Type: application/json' \
-d '{"username":"bob","password":"..."}'
Both calls return { "token": "...", "userId": ... }. Use the token as Authorization: Bearer <token> for all subsequent requests. After both accounts are created you can log in from the app at /login.
Importing into a NixOS system flake
The flake exposes three ways to import the module, all of which auto-wire the package.
Option A — lib.mkTasksModule (recommended when you want to set port/host in the flake)
Call mkTasksModule with your chosen defaults. These become lib.mkDefault values so you can still override them in configuration.nix.
# flake.nix on your NixOS host
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
tasks = {
url = "github:youruser/tasks"; # or "path:/path/to/tasks" for local
inputs.nixpkgs.follows = "nixpkgs"; # share nixpkgs to avoid double builds
};
};
outputs = { nixpkgs, tasks, ... }: {
nixosConfigurations.myhostname = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
(tasks.lib.mkTasksModule { port = 8001; address = "127.0.0.1"; })
./configuration.nix
];
};
};
}
Then in configuration.nix:
services.tasks.enable = true;
# port and address are already defaulted by mkTasksModule above;
# override here if needed.
Option B — pre-wired module (no port/host defaults)
nixosModules.tasks sets only the package. Configure port and address in your own module.
modules = [
tasks.nixosModules.tasks
./configuration.nix
];
# configuration.nix
services.tasks = {
enable = true;
port = 8001;
address = "127.0.0.1";
};
Option C — bare module + manual package
nixosModules.default is the raw module with no package pre-set, for when you supply your own build.
modules = [ tasks.nixosModules.default ];
# configuration.nix
services.tasks = {
enable = true;
package = tasks.packages.x86_64-linux.default;
port = 8001;
};
Service options
| Option | Type | Default | Description |
|---|---|---|---|
enable |
bool | false |
Enable the service |
port |
port | 8000 |
TCP port to listen on |
address |
string | "0.0.0.0" |
Bind address |
The SQLite database is stored at /var/lib/tasks/app.db (managed by systemd StateDirectory).
Traefik example
The service binds directly on port; put Traefik (or nginx) in front for TLS:
services.tasks = {
enable = true;
port = 8001;
address = "127.0.0.1"; # only listen on loopback; Traefik handles public traffic
};
# Traefik label equivalent in NixOS (dynamic config file or docker labels):
# rule: Host(`tasks.example.com`)
# service port: 8001
Updating the npm deps hash
After changing frontend/package.json and running npm install:
nix shell nixpkgs#prefetch-npm-deps --command \
prefetch-npm-deps frontend/package-lock.json
Paste the printed hash into flake.nix under npmDepsHash.