DEV Community

Cover image for DaloyJS Is the Backend Your React Native and Expo App Deserves
Daloy JS
Daloy JS

Posted on

DaloyJS Is the Backend Your React Native and Expo App Deserves

If you've built a React Native or Expo app before, you know the pain: the mobile side looks good, the UX flows well, and then you have to wire up a backend. Suddenly you're choosing between Express (fine but naked), Fastify (great but verbose), or something else that makes you write "boilerplate glue" for the next three days before writing a single real endpoint.

I've been in that situation more than once. And when I found DaloyJS, I felt a bit embarrassed at how much time I had wasted.

One Route Definition, Everything You Need

The core idea in DaloyJS is contract-first routing. You define a route once using a Zod schema, and the framework gives you validation, types, OpenAPI 3.1 docs, and a typed client. No separate spec file. No drift between what the backend returns and what the frontend expects.

Here is a real example you could drop into a BFF (Backend for Frontend) serving a React Native 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 });

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

app.route({
  method: "GET",
  path: "/user/:id",
  operationId: "getUserById",
  request: { params: z.object({ id: z.string().uuid() }) },
  responses: {
    200: {
      description: "User found",
      body: z.object({ id: z.string(), name: z.string(), avatarUrl: z.string().url() }),
    },
    404: { description: "Not found" },
  },
  handler: async ({ params }) => ({
    status: 200,
    body: { id: params.id, name: "Maria Santos", avatarUrl: "https://cdn.example.com/avatar.jpg" },
  }),
});

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

Notice what you did not have to write: a separate OpenAPI file, a validation middleware, a custom error handler for bad UUIDs. DaloyJS handles all of that.

The Typed Client Is the Real Game-Changer

For React Native and Expo apps living in a monorepo, DaloyJS's in-process typed client means your mobile code calls the backend with full TypeScript inference, zero codegen:

import { createClient } from "@daloyjs/core/client";
import { app } from "./server.js";

const client = createClient(app, { baseUrl: "https://api.yourapp.com" });

const r = await client.getUserById({ params: { id: "some-uuid" } });
if (r.status === 200) {
  console.log(r.body.name); // TypeScript knows this is a string
}
Enter fullscreen mode Exit fullscreen mode

No hand-maintained types. No guessing what the API returns at 11pm before a release.

Security Without the Extra Homework

The framework ships with secureHeaders, rate limiting, and prototype-pollution-safe JSON out of the box. For a mobile app backend that sits on the open internet, those are not optional. With DaloyJS, they are not an afterthought either.

Run It Anywhere

DaloyJS runs on Node, Bun, Deno, Cloudflare Workers, and Vercel Edge. That means your Expo app's BFF can start on a simple VPS and migrate to edge functions later without rewriting the app logic.


"Daloy" means flow in Tagalog. After shipping it in a side project, that name feels right. The code flows. The types flow. The deployment flows. Honestly, I wish I had this ten years ago.

Top comments (0)