DEV Community

Cover image for Why DaloyJS Is the Best Backend (or BFF) for Your Electron App
Daloy JS
Daloy JS

Posted on

Why DaloyJS Is the Best Backend (or BFF) for Your Electron App

If you've ever built an Electron app that needs a backend, you know the problem. You want something lightweight, something that runs on Node, something where you don't spend three hours configuring Swagger, and something that doesn't make you feel like you're setting up a microservices architecture just to expose two endpoints to your own desktop UI.

I've shipped Electron apps with Express. I've tried Fastify. Both work, but they leave you doing a lot of plumbing yourself. Then I found DaloyJS, and honestly, it clicked.

The Electron Backend Problem

Here's the thing about Electron: your renderer process is basically a browser, and your main process is a Node.js server. When you need data from external APIs, or you need a clean layer between your UI and your business logic, you want a BFF, a Backend for Frontend. A thin server that composes upstream calls, holds the session, and returns exactly the shape your UI needs.

DaloyJS was built for this role. The docs even say so plainly: typed upstream client, fetchGuard for safe egress, session handling, and edge runtime support. That combination is exactly the BFF toolkit.

Contract-First Means Less Glue

The killer feature for desktop apps is contract-first routing. You define a route once, and DaloyJS gives you validation, OpenAPI 3.1 docs, and a typed in-process client all from the same source. No stale spec files. No writing types by hand.

Here's what a basic Electron BFF route looks like:

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,
  docs: true, // auto-mounts /docs and /openapi.json
});

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

app.route({
  method: "GET",
  path: "/settings/:userId",
  operationId: "getUserSettings",
  request: { params: z.object({ userId: z.string() }) },
  responses: {
    200: {
      description: "OK",
      body: z.object({ theme: z.string(), language: z.string() }),
    },
  },
  handler: async ({ params }) => ({
    status: 200,
    body: { theme: "dark", language: "en" },
  }),
});

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

From the renderer, you call it with a typed client. No fetch boilerplate, no guessing the response shape:

import { createClient } from "@daloyjs/core/client";

const client = createClient(app, { baseUrl: "http://localhost:3123" });
const result = await client.getUserSettings({ params: { userId: "me" } });
// result.body is fully typed: { theme: string, language: string }
Enter fullscreen mode Exit fullscreen mode

That's the whole loop. One definition, end-to-end types.

Secure by Default, Which Matters More Than You Think

Desktop apps often access local files, internal network APIs, and cloud services at the same time. That's a juicy attack surface if your BFF is careless. DaloyJS starts with prototype-pollution-safe JSON parsing, built-in load shedding, automatic 5xx info-disclosure stripping in production, and fetchGuard that blocks SSRF and cloud-metadata IPs by default. I learned the hard way on a previous job that "just use Express" means you're also responsible for all that yourself.

Scaffold and Go

Getting started takes one command:

pnpm create daloy@latest my-electron-api
Enter fullscreen mode Exit fullscreen mode

The scaffold drops in a hardened .npmrc (blocked install scripts, 24h release-age cooldown, source-verified lockfiles) plus an AGENTS.md so your coding assistant actually understands the project conventions. For a solo dev building a desktop app on the side, that's a lot of boilerplate you never have to write.

The Bottom Line

DaloyJS is not trying to be the next Express. It's trying to remove the glue between the best ideas in the ecosystem: FastAPI-style docs, Hono-style portability, Fastify-style ops, and Elysia-level typing. For an Electron BFF running on Node, that combination is hard to beat. You get a typed contract, security defaults you'd otherwise forget, and auto-generated docs your future self will thank you for.

Start with pnpm create daloy@latest, wire it to your renderer, and stop writing boilerplate.

Top comments (0)