sushi-chaaaan / burntodo

A Todo App built with Hono and Remix. Connected via Service Bindings and Hono RPC.
https://burntodo.pages.dev
5 stars 3 forks source link

BurnTodoπŸ”₯

This is an example Todo App. Backend is written in HonoπŸ”₯ and Frontend is written in Remix.

Both are deployed on Cloudflare Workers / Pages, and connected via Service Bindings and Hono RPC.

Architectural diagram can be seen here.

Motivation

This architecture allows for independent and loosely coupled front-end and back-end implementations. On the other hand, the actual API calls are type-assisted by Hono RPC and communicated within Cloudflare via Service Bindings, making them secure and fast.

The resulting backend API call via Service Bindings looks like this. You can try this page at here.

import type { LoaderFunctionArgs } from "@remix-run/cloudflare";
import { useLoaderData } from "@remix-run/react";

export async function loader({ context }: LoaderFunctionArgs) {
  const api = getApi({ context }); // this is factory for hono/client
  return await api.hello
    .$get()
    .then((res) => res.json())
    .catch((err) => {
      console.error(err);
      return { error: "Failed to fetch data", message: null };
    });
}

export default function Route() {
  const data = useLoaderData<typeof loader>();

  return (
    <div>
      <h1>Response from backend</h1>
      <pre>
        <code>{JSON.stringify(data, null, 2)}</code>
      </pre>
    </div>
  );
}

Demo

Demo app is available at here.

TODO: add gif

Setup

pnpm install

frontend setup

cd apps/frontend
cp example.dev.vars .dev.vars

# put cookie secret for deploy
pnpm exec wrangler secret put COOKIE_SECRET

backend setup

  1. Create D1 databse.
cd apps/backend
pnpm exec wrangler d1 create <DATABASE_NAME>
  1. Paste output to apps/backend/wrangler.toml.
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "<DATABASE_NAME>"
database_id = "<DATABASE_ID>"
  1. Modify dbCredentials in apps/backend/drizzle.config.ts.
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  dbCredentials: {
    dbName: "<DATABASE_NAME>", // edit here!!!
    wranglerConfigPath: "./wrangler.toml",
  },
  driver: "d1",
  out: "./migrations",
  schema: "../../packages/module/src/schema.ts",
  strict: true,
  verbose: true,
});
  1. Put Password Salt.
pnpm exec wrangler secret put PASSWORD_SALT

Run Locally

  1. Follow setup section.

  2. Run pnpm run dev at project root.

Acknowledgements

Project Structure

.
β”œβ”€β”€ apps
β”‚   β”œβ”€β”€ backend
β”‚   β”‚   β”œβ”€β”€ drizzle.config.ts -> Drizzle ORM configuration
β”‚   β”‚   β”œβ”€β”€ package.json
β”‚   β”‚   β”œβ”€β”€ src
β”‚   β”‚   β”‚   └── index.ts      -> exports backend entrypoint from packages/api/src/server.ts
β”‚   β”‚   └── wrangler.toml     -> backend worker configuration
β”‚   └── frontend
β”‚       β”œβ”€β”€ app               -> Remix app
β”‚       β”œβ”€β”€ lib
β”‚       β”‚   └── api.ts        -> exports api client from packages/api/src/client.ts
β”‚       β”œβ”€β”€ load-context.ts
β”‚       β”œβ”€β”€ package.json
β”‚       β”œβ”€β”€ worker-configuration.d.ts -> types generated from wrangler.toml
β”‚       └── wrangler.toml     -> frontend pages configuration
β”œβ”€β”€ packages
β”‚   β”œβ”€β”€ api
β”‚   β”‚   β”œβ”€β”€ package.json
β”‚   β”‚   └──  src              -> backend api built with Hono
β”‚   └── module
β”‚       └──  src              -> core module interact with DB
β”œβ”€β”€ .gitignore
β”œβ”€β”€ .prettierignore
β”œβ”€β”€ eslint.config.mjs
β”œβ”€β”€ package.json
β”œβ”€β”€ README.md
β”œβ”€β”€ pnpm-lock.yaml
β”œβ”€β”€ pnpm-workspace.yaml
β”œβ”€β”€ prettier.config.mjs
β”œβ”€β”€ README.md
β”œβ”€β”€ tsconfig.json
└── turbo.json