Skip to content
This repository was archived by the owner on May 9, 2026. It is now read-only.

Framework Best Practices

Sloan edited this page Apr 8, 2026 · 25 revisions

Frontend Best Practices

1. Framework: Routing

Using expo-router allows you to manage navigation with built-in abstractions instead of manually handling screen state. This follows React Native conventions, supports nested screens, back navigation, and deep linking, and avoids reinventing routing logic. Components used in expo router are the following: Stack, useRouter, Tabs, usePathname, Link, useFocusEffect, useLocalSearchParams, type {Href}

Code Implementation

in frontend/app/(tabs)/_layout.tsx

import { Tabs, usePathname, useRouter } from "expo-router";

Picture2

Picture3

Picture1

2. Framework: UI Components

By using @/components/ui, we're taking advantage of a consistent, reusable set of prebuilt interface components across the application. This abstraction makes UI development easier since it provides the benefit of consistency in style, it avoids duplicated code, and it ensures visual consistency. The components utilize modern React Native design best practices and make developing interfaces faster. The components also prevent developers from having to build a UI component from scratch every time.

Code Implementation

in frontend/app/(tabs)/dashboard.tsx

import { Badge, Button, Icon, Text } from "@/components/ui";

image

image

3. Framework: Responsive Layouts (useWindowDimensions)

Using useWindowDimensions allows the UI to adapt dynamically to different screen sizes on web and mobile platforms. This avoids hardcoding dimensions, follows React Native best practices, and ensures consistent layouts across devices.

Code Implementation

in frontend/hooks/use-breakpoint.ts

import { useWindowDimensions } from "react-native";

image

image


Backend Best Practices

1. Framework: Centralized Routing & Middleware

Using Hono allows us to compose the API using a modular structure. We use a main instance to handle global concerns (CORS, Rate Limiting, Logging) and delegate specific logic to sub-apps via api.route(). This ensures that as the project grows, the index.ts remains a high-level overview.

Code Implementation

From backend/src/index.ts:

// We define a Type-Safe Context for User Sessions
const app = new Hono<{
  Variables: {
    user: typeof auth.$Infer.Session.user | null;
    session: typeof auth.$Infer.Session.session | null;
  };
}>();

// We compose the API using sub-routers
const api = new Hono();
api.route("/metrics", metricsApp);
api.route("/news", newsApp);

// We nest the entire API under a versioned prefix
app.route("/api", api);

2. Framework: Global Error & Validation Handling

Instead of using try-catch blocks in every single route, we use a Global Error Handler (app.onError). This specifically catches Zod validation errors and HTTPExceptions, ensuring the frontend always receives a consistent JSON error structure (status code, error message, and validation details) regardless of which route failed.

Code Implementation

From backend/src/index.ts:

app.onError((err, _c) => {
  if (err instanceof HTTPException) {
    const cause = err.cause;
    // Specific handling for Zod validation errors
    if (cause instanceof ZodError) {
      return jsonPretty({
        error: "Validation failed",
        details: cause.issues.map((i) => ({ path: i.path.join("."), message: i.message })),
      }, 400);
    }
  }
  // Standardized Internal Server Error
  return jsonPretty({ error: "Internal server error" }, 500);
});

3. Framework: Environment-Aware Middleware

We leverage Hono’s middleware pattern to apply different behaviors based on the deployment environment. For example, we use structured JSON logging in production for better observability in log aggregators, while keeping human-readable strings in development for a better developer experience.

Code Implementation

From backend/src/index.ts:

if (process.env.NODE_ENV === "production") {
  // Structured JSON logging for cloud monitoring
  app.use("*", async (c, next) => {
    await next();
    logInfo("HTTP Request", { method: c.req.method, path: c.req.path, status: c.res.status });
  });
} else {
  // Developer-friendly console logging
  app.use("*", logger((str) => logInfo(str)));
}

4. Framework: Type-Safe Schema & Migrations

We use Drizzle ORM to maintain a "single source of truth" for our database. By defining the schema in TypeScript, we gain auto-completion and compile-time error checking. We use a Singleton Pattern for the database client to prevent exhausting connection pools in a serverless or long-running environment.

Code Implementation

From backend/src/db/index.ts:

// We create a singleton pattern to reuse database connections
const globalForDb = globalThis as unknown as {
  client?: ReturnType<typeof postgres>;
  db?: ReturnType<typeof drizzle<typeof schema>>;
};

export const client = globalForDb.client ?? postgres(databaseUrl, { ssl: false });
if (process.env.NODE_ENV !== "production") globalForDb.client = client;

// We export the DB instance with the schema attached for relational queries
export const db = drizzle(client, { schema });

// We dedicate a client for migrations (restricted to 1 connection)
export const migrationDb = drizzle(postgres(databaseUrl, { max: 1 }), { schema });

5. Framework: Session Management

Instead of manually handling JWT tokens, we use the Better-Auth library. This centralizes all authentication logic, including email password resets and mobile deep-linking via the Expo plugin. By using the drizzleAdapter, Better-Auth automatically manages user, session, and account tables within our existing database.

Code Implementation

From backend/src/lib/auth.ts:

export const auth = betterAuth({
  // We integrate our database by syncing the auth tables with Drizzle
  database: drizzleAdapter(db, {
    provider: "pg",
  }),
  // The Expo plugin handles deep linking and mobile sessions
  plugins: [
    openAPI(), 
    expo(), 
  ],
  // Advanced cookie attributes for cross-domain production environments
  advanced: {
    defaultCookieAttributes: {
      sameSite: "none",
      secure: true,
      partitioned: process.env.NODE_ENV === "production",
    },
  },
  // We implement custom logic to hook into auth events like password resets
  emailAndPassword: {
    enabled: true,
    async sendResetPassword({ user, url }) {
      await sendEmail("Reset Your Password", <ResetPasswordTemplate url={url} />, { to: [user.email] });
    },
  },
});

N8N Best Practices

1. Framework:

Code Implementation

2. Framework:

Code Implementation

3. Framework:

Code Implementation

Clone this wiki locally