Why DaloyJS Is the Backend Your Tauri v2 Mobile App Deserves

javascript dev.to

If you're building a cross-platform app in Tauri v2 targeting both Android and iOS, you already made one smart decision: Rust for the shell, web tech for the UI, no Electron-sized download. Now you need a backend (or a BFF to front your internal services), and this is where most people make the regrettable choice of "just Express, it'll be fine."

It will not be fine. I've shipped this pattern. It starts clean and ends with a routes/index.js that nobody touches because nobody knows what it does anymore.

DaloyJS is a TypeScript REST framework built around one core idea: define the route once, get everything else for free. Validation, OpenAPI 3.1 docs, a typed client, and a hardened security baseline all come from a single route declaration. No stitching, no stale Swagger files, no "oh the schema and the handler drifted again."

The BFF Match Is Almost Too Good

The docs describe the BFF pattern as "arguably where DaloyJS is at its best," and after reading through the framework, I believe it. A Tauri v2 mobile app needs a backend layer that: handles session state cleanly, fans out to internal or third-party services, and returns exactly the shape the UI needs, not everything from the database. DaloyJS ships all of this as first-party pieces: session(), csrf(), fetchGuard() (which blocks SSRF and private-CIDR calls by default), and typed upstream clients via Hey API codegen.

The typed client part is where I'd have saved two weeks at a previous job. You run pnpm gen, and your Tauri app's frontend gets a fully typed fetch SDK generated from your actual OpenAPI spec. No copy-pasted types, no "wait, did we change that field?"

Here's a minimal BFF route that would serve a Tauri mobile app's product screen:

import { z } from "zod";
import { App, requestId, secureHeaders, rateLimit } from "@daloyjs/core";
import { serve } from "@daloyjs/core/node";

const app = new App({
  bodyLimitBytes: 1 << 20,
  requestTimeoutMs: 5_000,
  openapi: { info: { title: "My App BFF", version: "1.0.0" } },
  docs: true, // mounts GET /docs and GET /openapi.json automatically
});

app.use(requestId());
app.use(secureHeaders());
app.use(rateLimit({ windowMs: 60_000, max: 120 }));

app.route({
  method: "GET",
  path: "/products/:id",
  operationId: "getProduct",
  tags: ["Products"],
  request: {
    params: z.object({ id: z.string().uuid() }),
  },
  responses: {
    200: {
      description: "Product found",
      body: z.object({
        id: z.string(),
        name: z.string(),
        price: z.number(),
      }),
    },
    404: { description: "Not found" },
  },
  handler: async ({ params }) => ({
    status: 200,
    body: { id: params.id, name: "Wireless Headphones", price: 79.99 },
  }),
});

serve(app, { port: 3000 });
Enter fullscreen mode Exit fullscreen mode

One route definition. Validation, OpenAPI, typed handler, and the docs UI at /docs, no extra wiring needed.

Security Is the Default, Not the Checklist

Mobile apps are not browsers. Your Tauri Android and iOS clients are talking to your backend over networks you don't control. DaloyJS starts with prototype-pollution-safe JSON parsing, automatic 5xx info-disclosure stripping in production, and built-in load shedding. You don't need to remember to add these. They're already there.

For a BFF specifically, the fetchGuard() middleware is worth calling out: it blocks outbound calls to private CIDRs and cloud-metadata endpoints by default. I learned the hard way (in a previous job, not a fun postmortem) that SSRF via a BFF is a real attack surface. Here, the safe path is the default path.

Runtime Portability Is Not Just a Bullet Point

The DaloyJS core only knows Request and Response. The same app runs on Node, Bun, Deno, Cloudflare Workers, and Vercel Edge by swapping the adapter import. For a Tauri app in early stages, you might start on Node. When you need to scale or cut cold start times for mobile users in Southeast Asia (hi, my target market), you move to Cloudflare Workers without rewriting your routes.

That's the kind of architectural flexibility that makes a framework worth investing in.


Check out daloyjs.dev and scaffold your first project with pnpm create daloy@latest my-bff.

Source: dev.to

arrow_back Back to Tutorials