Skip to content

feat(): add hono example#8317

Merged
mhartington merged 1 commit intolatestfrom
feat/hono
Oct 9, 2025
Merged

feat(): add hono example#8317
mhartington merged 1 commit intolatestfrom
feat/hono

Conversation

@mhartington
Copy link
Member

@mhartington mhartington commented Oct 6, 2025

Summary by CodeRabbit

  • New Features

    • Adds a Hono + Prisma REST API template: user signup, post CRUD, feed, drafts, and related endpoints.
  • Documentation

    • Adds a step-by-step setup guide covering database init, DATABASE_URL, migrations, seeding, and running the dev server.
  • Chores

    • Adds env example, project scripts, Prisma schema, seed data, TypeScript config, and Prisma integration wiring.
  • Tests

    • Adds an automated script to provision the DB, run migrations/seeds, start the app, smoke-test endpoints, and clean up.

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Walkthrough

Adds a self-contained Hono + Prisma Postgres REST API example under orm/hono: project scaffold, Prisma schema and seed, Prisma middleware using the Accelerate extension, a Hono server with CRUD endpoints and feed, a .env example, README, and a CI test/run script.

Changes

Cohort / File(s) Summary
Project scaffold & docs
orm/hono/package.json, orm/hono/tsconfig.json, orm/hono/.env.example, orm/hono/README.md
New Node/TypeScript project files: package scripts (dev/build/start, prisma seed), TypeScript config, .env.example with DATABASE_URL placeholder, and README with setup/run instructions.
Prisma schema & seed
orm/hono/prisma/schema.prisma, orm/hono/prisma/seed.ts
Adds Prisma Client generator (output ../src/generated/prisma), PostgreSQL datasource using env("DATABASE_URL"), User and Post models with relations and defaults, and a seed script creating sample users with nested posts.
Server & middleware
orm/hono/src/index.ts, orm/hono/src/lib/prisma.ts
Adds Hono app exposing REST endpoints (signup, post CRUD, publish/views, feed, users) and withPrisma middleware that loads dotenv, requires DATABASE_URL, initializes/caches a PrismaClient extended with the Accelerate extension, and injects it into request context.
Tests / CI helper
.github/tests/orm/hono/run.sh
New test helper to start Prisma Dev, wait for a DATABASE_URL in logs, export it, run migrations and seed, start the Hono app, verify connectivity, and clean up background processes on success or timeout.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor Client
    participant Server as Hono App
    participant MW as withPrisma
    participant PC as PrismaClient
    participant DB as PostgreSQL

    Client->>Server: HTTP request (e.g., GET /feed)
    Server->>MW: enter middleware
    MW->>PC: init or reuse PrismaClient (reads DATABASE_URL, attach Accelerate)
    MW-->>Server: next()
    Server->>PC: execute query (e.g., post.findMany)
    PC->>DB: SQL/query
    DB-->>PC: result
    PC-->>Server: data
    Server-->>Client: 200 JSON
Loading

Suggested reviewers

  • nurul3101
  • mhessdev

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat(): add hono example” succinctly and accurately describes the primary change by indicating that a new Hono example is being added to the repository, making it clear and focused on the main feature introduced.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/hono

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AmanVarshney01
Copy link
Member

AmanVarshney01 commented Oct 6, 2025

We can also include a .env.example with the DATABASE_URL, right? All the other templates have it too. 🤔

The dotenv() call should be in src/lib/prisma.ts where the database URL is used. Otherwise, I’m getting a 'DATABASE_URL not found' error.

@mhartington
Copy link
Member Author

updated @AmanVarshney01

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f42cfab and 13fa49e.

📒 Files selected for processing (8)
  • orm/hono/.env.example (1 hunks)
  • orm/hono/README.md (1 hunks)
  • orm/hono/package.json (1 hunks)
  • orm/hono/prisma/schema.prisma (1 hunks)
  • orm/hono/prisma/seed.ts (1 hunks)
  • orm/hono/src/index.ts (1 hunks)
  • orm/hono/src/lib/prisma.ts (1 hunks)
  • orm/hono/tsconfig.json (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T12:12:43.161Z
Learnt from: FGoessler
PR: prisma/prisma-examples#8260
File: generator-prisma-client/nextjs-starter-turbopack/components/quotes.tsx:1-1
Timestamp: 2025-08-22T12:12:43.161Z
Learning: In Next.js applications using Prisma, the connection() function from 'next/server' serves a different purpose than 'export const dynamic = force-dynamic'. The connection() function ensures proper database connection context and request isolation, while dynamic = 'force-dynamic' only controls rendering strategy. Both should be used together and are not redundant.

Applied to files:

  • orm/hono/src/lib/prisma.ts
🪛 dotenv-linter (3.3.0)
orm/hono/.env.example

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 markdownlint-cli2 (0.18.1)
orm/hono/README.md

11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


17-17: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


31-31: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


132-132: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test (orm/nest-graphql-sdl-first)
  • GitHub Check: test (orm/betterauth-nextjs)
  • GitHub Check: test (orm/nest-graphql)
  • GitHub Check: test (orm/grpc)
  • GitHub Check: test (orm/nest)
  • GitHub Check: test (orm/nuxt)
  • GitHub Check: test (orm/solid-start)
🔇 Additional comments (5)
orm/hono/tsconfig.json (1)

1-16: LGTM!

The TypeScript configuration is appropriate for a Hono + Node.js project with strict type checking and proper JSX handling for Hono's JSX runtime.

orm/hono/package.json (1)

1-25: LGTM!

The package configuration is well-structured with appropriate dependencies, matching Prisma versions in dependencies and devDependencies, and proper script setup for development, build, and start workflows.

orm/hono/src/index.ts (1)

1-30: LGTM!

The server implementation is well-structured with proper TypeScript typing for the Prisma-enhanced context, correct middleware usage, and clean route definitions. The Hono app correctly applies the withPrisma middleware to inject the Prisma Client into the request context.

orm/hono/src/lib/prisma.ts (1)

5-6: Verify the DATABASE_URL loading works correctly.

Based on the PR comment by AmanVarshney01, the current placement of dotenv.config() reportedly causes a "DATABASE_URL not found" error. The placement here at the top of the module should work correctly, as it loads environment variables before line 8 reads process.env.DATABASE_URL.

Please confirm:

  1. Does a .env file exist in the project root with DATABASE_URL set?
  2. Is the error occurring during npm run dev or npm run build?
  3. What is the exact error message?

If the issue persists, try moving the dotenv import and config to the very top of src/index.ts before any other imports to ensure environment variables are loaded before any module initialization.

orm/hono/prisma/schema.prisma (1)

1-32: LGTM!

The Prisma schema is well-designed with proper model relationships, appropriate constraints (unique email, autoincrement IDs), sensible defaults, and a custom output path for the generated client that aligns with the project structure.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13fa49e and c384a67.

📒 Files selected for processing (1)
  • .github/tests/orm/hono/run.sh (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (orm/grpc)
  • GitHub Check: test (orm/nest-graphql-sdl-first)
  • GitHub Check: test (orm/nuxt)
  • GitHub Check: test (orm/nest-graphql)
  • GitHub Check: test (orm/solid-start)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

♻️ Duplicate comments (8)
orm/hono/prisma/seed.ts (1)

51-57: Add error handling and ensure proper cleanup.

The seed script still lacks error handling and doesn't disconnect the Prisma Client, which can leave database connections open.

.github/tests/orm/hono/run.sh (7)

3-3: Harden shell: add pipefail and trap-based cleanup to avoid masked errors/orphans.

The script still uses set -eu without handling pipeline failures or ensuring background process cleanup.


17-18: Use npm ci for reproducible CI installs.

The script still uses npm install instead of npm ci for CI environments.


49-51: Do not print DATABASE_URL (secret) to logs.

The script still echoes the full DATABASE_URL which leaks credentials in CI logs.


56-56: Migration strategy: prefer idempotent apply for CI.

The script still uses migrate dev which may prompt and isn't ideal for CI.


59-62: Fix message and set PORT explicitly.

The message still says "Starting Hono app" but could benefit from explicit PORT setting.


64-68: Replace static sleep with readiness probe.

The script still uses a 20-second sleep instead of actively waiting for the server to be ready.


71-74: Make cleanup resilient; don't fail if processes already exited.

The cleanup logic doesn't handle cases where processes have already exited.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c384a67 and 46e9323.

⛔ Files ignored due to path filters (11)
  • .github/get-ppg-dev/ppg-dev-url.log is excluded by !**/*.log
  • orm/hono/src/generated/prisma/browser.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/client.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/commonInputTypes.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/enums.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/internal/class.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/internal/prismaNamespace.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/internal/prismaNamespaceBrowser.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/models.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/models/Post.ts is excluded by !**/generated/**
  • orm/hono/src/generated/prisma/models/User.ts is excluded by !**/generated/**
📒 Files selected for processing (3)
  • .github/tests/orm/hono/run.sh (1 hunks)
  • orm/hono/prisma/seed.ts (1 hunks)
  • orm/hono/src/index.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-22T12:12:24.602Z
Learnt from: FGoessler
PR: prisma/prisma-examples#8260
File: generator-prisma-client/nextjs-starter-webpack-turborepo/packages/database/package.json:13-13
Timestamp: 2025-08-22T12:12:24.602Z
Learning: When analyzing changes from package.json seed scripts to "prisma db seed", always verify the actual content of prisma.config.ts rather than relying solely on regex patterns, as the configuration may be properly defined but in a format that doesn't match overly restrictive search patterns.

Applied to files:

  • orm/hono/prisma/seed.ts
🧬 Code graph analysis (2)
orm/hono/prisma/seed.ts (3)
orm/hono/src/generated/prisma/client.ts (3)
  • PrismaClient (37-37)
  • PrismaClient (38-38)
  • Prisma (39-39)
orm/hono/src/generated/prisma/internal/class.ts (1)
  • PrismaClient (115-222)
orm/hono/src/generated/prisma/models/User.ts (1)
  • UserCreateInput (242-246)
orm/hono/src/index.ts (3)
orm/hono/src/generated/prisma/client.ts (3)
  • PrismaClient (37-37)
  • PrismaClient (38-38)
  • Prisma (39-39)
orm/hono/src/generated/prisma/models/Post.ts (1)
  • PostCreateInput (270-275)
orm/hono/src/generated/prisma/internal/prismaNamespace.ts (2)
  • SortOrder (619-622)
  • SortOrder (624-624)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: test (orm/nest)
  • GitHub Check: test (orm/grpc)
  • GitHub Check: test (orm/astro)
  • GitHub Check: test (orm/nest-graphql-sdl-first)
  • GitHub Check: test (orm/nextjs-graphql)
  • GitHub Check: test (orm/nuxt)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (8)
orm/hono/src/index.ts (8)

65-83: Fix HTTP status code for update operation.

The endpoint returns 201 Created, but updates should return 200 OK.

Apply this diff:

     const post = await prisma.post.update({
       where: { id },
       data: { viewCount: { increment: 1 } },
     });
-    return c.json(post, 201);
+    return c.json(post, 200);

189-191: Make server port configurable via environment variable.

The port is hardcoded to 3000, which limits deployment flexibility.

Apply this diff:

-serve({ fetch: app.fetch, port: 3000 }, (info) => {
+const port = Number(process.env.PORT) || 3000;
+serve({ fetch: app.fetch, port }, (info) => {
   console.log(`Server is running on http://localhost:${info.port}`);
 });

132-138: Consider adding pagination to prevent unbounded result sets.

Fetching all users without limits could cause performance issues as data grows.

Apply this diff to add pagination:

 app.get('/users', withPrisma, async (c) => {
   const prisma = c.get('prisma');
+  const { skip, take } = c.req.query();
   const users = await prisma.user.findMany({
     include: { posts: true },
+    skip: skip ? Number(skip) : undefined,
+    take: take ? Number(take) : undefined,
   });
   return c.json(users);
 });

152-161: Add 404 handling for missing posts.

The endpoint returns null when a post doesn't exist instead of a proper 404 response.

Apply this diff:

 app.get('/post/:id', withPrisma, async (c) => {
   const prisma = c.get('prisma');
   const id = Number(c.req.param('id'));
   const post = await prisma.post.findUnique({
     where: {
       id,
     },
   });
-  return c.json(post);
+  if (!post) {
+    return c.json({ error: `Post with ID ${id} not found` }, 404);
+  }
+  return c.json(post);
 });

17-42: Add input validation and error handling for database constraints.

The endpoint lacks validation and doesn't handle Prisma errors such as duplicate email violations (P2002).

Apply this diff to add validation and error handling:

 app.post('/signup', withPrisma, async (c) => {
   const prisma = c.get('prisma');
   const { name, email, posts } = await c.req.json<{
     name: string;
     email: string;
     posts: Array<PostCreateInput>;
   }>();
 
+  if (!email || typeof email !== 'string') {
+    return c.json({ error: 'Valid email is required' }, 400);
+  }
+
   const postData = posts
     ? posts.map((post: Prisma.PostCreateInput) => {
         return { title: post.title, content: post.content || undefined };
       })
     : [];
 
-  const newUser = await prisma.user.create({
-    data: {
-      name,
-      email,
-      posts: {
-        create: postData,
-      },
-    },
-  });
-
-  return c.json(newUser, 201);
+  try {
+    const newUser = await prisma.user.create({
+      data: {
+        name,
+        email,
+        posts: {
+          create: postData,
+        },
+      },
+    });
+    return c.json(newUser, 201);
+  } catch (error: any) {
+    if (error.code === 'P2002') {
+      return c.json({ error: 'Email already exists' }, 409);
+    }
+    throw error;
+  }
 });

85-108: Fix status code and optimize database operations.

Two issues:

  1. Wrong HTTP status code: updates should return 200, not 201.
  2. Unnecessary findUnique: the query can be eliminated by handling the error from update.

Apply this diff to optimize:

 app.put('/publish/:id', withPrisma, async (c) => {
   const prisma = c.get('prisma');
   const id = Number(c.req.param('id'));
-  const postToUpdate = await prisma.post.findUnique({
-    where: { id },
-  });
-
-  if (!postToUpdate) {
-    c.status(404);
-    return c.json({
-      error: `Post with ID ${id} does not exist in the database`,
-    });
-  }
-
-  const updatedPost = await prisma.post.update({
-    where: {
-      id,
-    },
-    data: {
-      published: !postToUpdate.published,
-    },
-  });
-  return c.json(updatedPost, 201);
+
+  try {
+    const post = await prisma.post.findUnique({ where: { id } });
+    if (!post) {
+      return c.json({ error: `Post with ID ${id} does not exist in the database` }, 404);
+    }
+    const updatedPost = await prisma.post.update({
+      where: { id },
+      data: { published: !post.published },
+    });
+    return c.json(updatedPost, 200);
+  } catch {
+    return c.json({ error: `Post with ID ${id} does not exist in the database` }, 404);
+  }
 });

44-63: Add input validation and handle missing author.

The endpoint lacks validation and doesn't handle the case where the author email doesn't exist in the database.

Apply this diff to add validation and error handling:

 app.post('/post', withPrisma, async (c) => {
   const prisma = c.get('prisma');
   const {
     title,
     content,
     authorEmail: email,
   } = await c.req.json<{
     title: string;
     content: string;
     authorEmail: string;
   }>();
+
+  if (!title || typeof title !== 'string') {
+    return c.json({ error: 'Title is required' }, 400);
+  }
+  if (!email || typeof email !== 'string') {
+    return c.json({ error: 'Author email is required' }, 400);
+  }
+
-  const newPost = await prisma.post.create({
-    data: {
-      title,
-      content,
-      author: { connect: { email } },
-    },
-  });
-  return c.json(newPost, 201);
+  try {
+    const newPost = await prisma.post.create({
+      data: {
+        title,
+        content,
+        author: { connect: { email } },
+      },
+    });
+    return c.json(newPost, 201);
+  } catch (error: any) {
+    if (error.code === 'P2025') {
+      return c.json({ error: 'Author not found' }, 404);
+    }
+    throw error;
+  }
 });

163-183: Improve search functionality with case-insensitive matching.

The current search is case-sensitive, which may not match user expectations for text search.

Apply this diff to make search case-insensitive:

   const or = searchString
     ? {
         OR: [
-          { title: { contains: searchString as string } },
-          { content: { contains: searchString as string } },
+          { title: { contains: searchString as string, mode: 'insensitive' } },
+          { content: { contains: searchString as string, mode: 'insensitive' } },
         ],
       }
     : {};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46e9323 and 5d60949.

📒 Files selected for processing (2)
  • orm/hono/prisma/schema.prisma (1 hunks)
  • orm/hono/src/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
orm/hono/src/index.ts (1)
orm/graphql/src/schema/post.ts (1)
  • PostCreateInput (18-23)
🔇 Additional comments (1)
orm/hono/prisma/schema.prisma (1)

1-35: Schema is well-structured.

The Prisma schema is correctly configured with:

  • Proper generator and datasource setup
  • Well-defined User and Post models with appropriate relations
  • Sensible defaults (published: false, viewCount: 0)
  • Required constraints (email unique)

The viewCount field at line 32 properly supports the view tracking endpoint in the API.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (17)
orm/hono/.env.example (1)

2-2: Remove unnecessary quotes from the placeholder value.

The empty string quotes are unnecessary and flagged by dotenv-linter. An empty value without quotes is clearer and avoids potential parsing issues.

Apply this diff:

-DATABASE_URL=""
+DATABASE_URL=
orm/hono/prisma/seed.ts (1)

51-57: Add error handling and ensure proper cleanup.

The seed script lacks error handling and doesn't disconnect the Prisma Client, which can leave database connections open. This is especially important for seed scripts that run and exit.

Apply this diff to add proper error handling and cleanup:

-export async function main() {
-  for (const u of userData) {
-    await prisma.user.create({ data: u });
-  }
-}
-
-main();
+export async function main() {
+  try {
+    for (const u of userData) {
+      await prisma.user.create({ data: u });
+    }
+  } catch (error) {
+    console.error('Error seeding database:', error);
+    throw error;
+  } finally {
+    await prisma.$disconnect();
+  }
+}
+
+main();
orm/hono/src/lib/prisma.ts (1)

9-21: Critical: Use a singleton PrismaClient instead of per-context instantiation.

The current implementation creates a new PrismaClient for each request context that doesn't already have one cached, which is inefficient and can lead to connection pool exhaustion. The c.set('prisma', prisma) caches the client on the current request context, but each new request will create a new client.

PrismaClient should be instantiated once at the module level and reused across all requests. This is the recommended pattern for serverless and long-running server environments.

Apply this diff to implement the singleton pattern:

 import type { Context, Next } from 'hono'
 import { PrismaClient } from '../generated/prisma/client.js'
 import { withAccelerate } from '@prisma/extension-accelerate'
 
 import * as dotenv from 'dotenv'
 dotenv.config()
 
 const databaseUrl = process.env.DATABASE_URL
+
+if (!databaseUrl) {
+  throw new Error('DATABASE_URL is not set')
+}
+
+// Create a singleton PrismaClient instance
+const prisma = new PrismaClient({ datasourceUrl: databaseUrl }).$extends(
+  withAccelerate(),
+)
+
 function withPrisma(c: Context, next: Next) {
-  if (!c.get('prisma')) {
-    if (!databaseUrl) {
-      throw new Error('DATABASE_URL is not set')
-    }
-    const prisma = new PrismaClient({ datasourceUrl: databaseUrl }).$extends(
-      withAccelerate(),
-    )
-
-    c.set('prisma', prisma)
-  }
+  c.set('prisma', prisma)
   return next()
 }
+
 export default withPrisma
orm/hono/README.md (1)

11-11: Add language specifiers to code blocks.

Several code blocks are missing language identifiers, which prevents proper syntax highlighting and reduces accessibility.

Apply these changes:

Line 11:

-```
+```bash
 npx try-prisma@latest --template orm/hono --install npm --name hono

Line 17:
```diff
-```
+```bash
 cd hono

Line 25:
```diff
-```
+```bash
 git clone git@github.com:prisma/prisma-examples.git --depth=1

Line 31:
```diff
-```
+```bash
 cd prisma-examples/orm/hono
 npm install

Line 132:
```diff
-```
+```bash
 npm run dev

Also applies to: 17-17, 25-25, 31-31, 132-132

</blockquote></details>
<details>
<summary>.github/tests/orm/hono/run.sh (7)</summary><blockquote>

`3-3`: **Harden shell: add pipefail and trap-based cleanup to avoid masked errors/orphans.**

Prevents pipeline errors from being ignored and ensures background processes are cleaned up on failures.




Apply this diff:

```diff
-set -eu
+set -euo pipefail
+
+# Cleanup background processes on exit
+cleanup() {
+  set +e
+  if [[ -n "${pid:-}" ]]; then
+    kill "$pid" 2>/dev/null || true
+    wait "$pid" 2>/dev/null || true
+  fi
+  if [[ -n "${NODE_PID:-}" ]]; then
+    kill "$NODE_PID" 2>/dev/null || true
+    wait "$NODE_PID" 2>/dev/null || true
+  fi
+}
+trap cleanup EXIT

17-19: Use npm ci for reproducible CI installs.

Locks deps to lockfile and speeds up installs.

Apply this diff (ensure package-lock.json exists in both dirs):

-npm install
+npm ci

Also applies to: 23-23


49-52: Do not print DATABASE_URL (secret) to logs.

This leaks credentials in CI logs. Redact or remove.

Apply this diff:

 export DATABASE_URL="$DB_URL"
-echo "✅ DATABASE_URL: $DATABASE_URL"
+echo "✅ Received DATABASE_URL"

56-57: Migration strategy: prefer idempotent apply for CI.

migrate dev creates migrations and may prompt; in CI examples, db push (no migrations) or migrate deploy (with checked-in migrations) is typical.

Pick one based on the example:

  • If no migrations are committed:
-npx prisma migrate dev --name init
+npx prisma db push
  • If migrations are committed:
-npx prisma migrate dev --name init
+npx prisma migrate deploy

59-63: Fix message and set PORT explicitly.

Ensure consistent naming and stable port for tests.

Apply this diff:

-# Start the app
-echo "🚀 Starting Hapi app..."
-npm run dev &
+# Start the app
+echo "🚀 Starting Hono app..."
+export PORT="${PORT:-3000}"
+npm run dev &

64-69: Replace static sleep with readiness probe.

Avoid flaky timing; wait until the server is actually up before running tests.

Apply this diff:

-sleep 15
-
-# Check frontend
-echo "🔎 Verifying root frontend route..."
-curl --fail 'http://localhost:3000/'
+# Wait for app readiness
+echo "⏳ Waiting for Hono app to be ready on :${PORT:-3000}..."
+MAX_WAIT_APP=60
+WAITED_APP=0
+until curl -sf "http://127.0.0.1:${PORT:-3000}/" >/dev/null; do
+  sleep 1
+  WAITED_APP=$((WAITED_APP + 1))
+  if [ "$WAITED_APP" -ge "$MAX_WAIT_APP" ]; then
+    echo "❌ App did not become ready on :${PORT:-3000}"
+    exit 1
+  fi
+done
+
+# Check frontend
+echo "🔎 Verifying root frontend route..."
+curl --fail 'http://localhost:3000/'

71-74: Make cleanup resilient; don't fail if processes already exited.

Add guards and wait for both PIDs.

Apply this diff (if you didn't add the trap above):

-echo "🛑 Shutting down Hono app (PID $pid) and Prisma Dev (PID $NODE_PID)..."
-kill "$pid"
-kill "$NODE_PID"
-wait "$NODE_PID" || true
+echo "🛑 Shutting down Hono app (PID ${pid:-}) and Prisma Dev (PID ${NODE_PID:-})..."
+kill "${pid:-}" 2>/dev/null || true
+kill "${NODE_PID:-}" 2>/dev/null || true
+wait "${pid:-}" 2>/dev/null || true
+wait "${NODE_PID:-}" 2>/dev/null || true

If you added the trap-based cleanup, you can simply rely on it and remove this explicit block.

orm/hono/src/index.ts (6)

17-41: Handle signup validation and duplicate email errors

Line 19 allows name, email, or posts to be missing or of the wrong type, so Prisma throws a 500. Duplicate emails also surface as raw P2002 errors. Validate the payload up front (return 400/422) and map the unique constraint to a 409 before calling Prisma.

 app.post('/signup', withPrisma, async (c) => {
   const prisma = c.get('prisma');
-  const { name, email, posts } = await c.req.json<{
-    name: string;
-    email: string;
-    posts: Array<PostCreateInput>;
-  }>();
+  const body = await c.req.json<{
+    name?: string;
+    email?: string;
+    posts?: unknown;
+  }>();
+  const { name, email, posts } = body;
+
+  if (!name || typeof name !== 'string' || !name.trim()) {
+    return c.json({ error: 'Name is required' }, 400);
+  }
+  if (!email || typeof email !== 'string' || !email.trim()) {
+    return c.json({ error: 'Valid email is required' }, 400);
+  }
+  if (posts && !Array.isArray(posts)) {
+    return c.json({ error: 'Posts must be an array' }, 400);
+  }
 
   const postData = posts
     ? posts.map((post: Prisma.PostCreateInput) => {
         return { title: post.title, content: post.content || undefined };
       })
     : [];
 
-  const newUser = await prisma.user.create({
-    data: {
-      name,
-      email,
-      posts: {
-        create: postData,
-      },
-    },
-  });
-
-  return c.json(newUser, 201);
+  try {
+    const newUser = await prisma.user.create({
+      data: {
+        name,
+        email,
+        posts: {
+          create: postData,
+        },
+      },
+    });
+    return c.json(newUser, 201);
+  } catch (error) {
+    if (
+      error instanceof Prisma.PrismaClientKnownRequestError &&
+      error.code === 'P2002'
+    ) {
+      return c.json({ error: 'Email already exists' }, 409);
+    }
+    throw error;
+  }
 });

44-63: Validate /post payload and return 404 when author is missing

Line 46 trusts the request body, so missing/invalid title/authorEmail values explode as 500s. If the author email doesn’t exist, Prisma throws P2025; currently that bubbles as 500 instead of 404. Validate inputs and catch P2025.

 app.post('/post', withPrisma, async (c) => {
   const prisma = c.get('prisma');
-  const {
-    title,
-    content,
-    authorEmail: email,
-  } = await c.req.json<{
-    title: string;
-    content: string;
-    authorEmail: string;
-  }>();
-  const newPost = await prisma.post.create({
-    data: {
-      title,
-      content,
-      author: { connect: { email } },
-    },
-  });
-  return c.json(newPost, 201);
+  const body = await c.req.json<{
+    title?: string;
+    content?: string;
+    authorEmail?: string;
+  }>();
+  const { title, content, authorEmail } = body;
+
+  if (!title || typeof title !== 'string' || !title.trim()) {
+    return c.json({ error: 'Title is required' }, 400);
+  }
+  if (!authorEmail || typeof authorEmail !== 'string' || !authorEmail.trim()) {
+    return c.json({ error: 'Author email is required' }, 400);
+  }
+
+  try {
+    const newPost = await prisma.post.create({
+      data: {
+        title,
+        content,
+        author: { connect: { email: authorEmail } },
+      },
+    });
+    return c.json(newPost, 201);
+  } catch (error) {
+    if (
+      error instanceof Prisma.PrismaClientKnownRequestError &&
+      error.code === 'P2025'
+    ) {
+      return c.json({ error: 'Author not found' }, 404);
+    }
+    throw error;
+  }
 });

65-75: Return 200 for updates and narrow error handling

Line 74 sends 201 (Created) even though the endpoint only updates an existing record. Also, the bare catch returns 404 for every failure, masking real server errors. Return 200 on success and only translate Prisma’s “record not found” into 404.

   try {
     const post = await prisma.post.update({
       where: { id },
       data: { viewCount: { increment: 1 } },
     });
-    return c.json(post, 201);
-  } catch {
-    return c.json(
-      {
-        error: `Post with ID ${id} does not exist in the database`,
-      },
-      404,
-    );
+    return c.json(post, 200);
+  } catch (error) {
+    if (
+      error instanceof Prisma.PrismaClientKnownRequestError &&
+      error.code === 'P2025'
+    ) {
+      return c.json(
+        { error: `Post with ID ${id} does not exist in the database` },
+        404,
+      );
+    }
+    throw error;
   }

85-108: Use a single update with proper error handling and status

Line 107 returns 201 for an update, and the separate findUnique + update still throws a 500 if the record disappears between calls. Perform one update inside a try/catch, return 200, and translate Prisma’s P2025 into 404.

 app.put('/publish/:id', withPrisma, async (c) => {
   const prisma = c.get('prisma');
   const id = Number(c.req.param('id'));
-  const postToUpdate = await prisma.post.findUnique({
-    where: { id },
-  });
-
-  if (!postToUpdate) {
-    c.status(404);
-    return c.json({
-      error: `Post with ID ${id} does not exist in the database`,
-    });
-  }
-
-  const updatedPost = await prisma.post.update({
-    where: {
-      id,
-    },
-    data: {
-      published: !postToUpdate.published,
-    },
-  });
-  return c.json(updatedPost, 201);
+  try {
+    const post = await prisma.post.update({
+      where: { id },
+      data: { published: { not: undefined } },
+    });
+    return c.json({ ...post, published: !post.published }, 200);
+  } catch (error) {
+    if (
+      error instanceof Prisma.PrismaClientKnownRequestError &&
+      error.code === 'P2025'
+    ) {
+      return c.json(
+        { error: `Post with ID ${id} does not exist in the database` },
+        404,
+      );
+    }
+    throw error;
+  }
 });

110-129: Don’t turn every delete failure into 404

The current catch treats any exception as “not found.” That hides real DB errors (e.g., connectivity issues) and sends clients the wrong status. Only map Prisma’s P2025 “record not found” to 404 and rethrow (or 500) everything else.

-  try {
+  try {
     const deletedPost = await prisma.post.delete({
       where: {
         id,
       },
     });
 
     return c.json(deletedPost);
-  } catch {
-    return c.json(
-      {
-        error: `Post with ID ${id} does not exist in the database`,
-      },
-      404,
-    );
+  } catch (error) {
+    if (
+      error instanceof Prisma.PrismaClientKnownRequestError &&
+      error.code === 'P2025'
+    ) {
+      return c.json(
+        { error: `Post with ID ${id} does not exist in the database` },
+        404,
+      );
+    }
+    throw error;
   }

152-160: Return 404 when a post isn’t found

findUnique returns null for missing posts, but Line 160 still answers 200 with null, forcing clients to guess. Respond with 404 in that case.

   const post = await prisma.post.findUnique({
     where: {
       id,
     },
   });
-  return c.json(post);
+  if (!post) {
+    return c.json({ error: `Post with ID ${id} not found` }, 404);
+  }
+  return c.json(post, 200);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 562c99e and 8df8248.

📒 Files selected for processing (9)
  • .github/tests/orm/hono/run.sh (1 hunks)
  • orm/hono/.env.example (1 hunks)
  • orm/hono/README.md (1 hunks)
  • orm/hono/package.json (1 hunks)
  • orm/hono/prisma/schema.prisma (1 hunks)
  • orm/hono/prisma/seed.ts (1 hunks)
  • orm/hono/src/index.ts (1 hunks)
  • orm/hono/src/lib/prisma.ts (1 hunks)
  • orm/hono/tsconfig.json (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-22T12:12:43.161Z
Learnt from: FGoessler
PR: prisma/prisma-examples#8260
File: generator-prisma-client/nextjs-starter-turbopack/components/quotes.tsx:1-1
Timestamp: 2025-08-22T12:12:43.161Z
Learning: In Next.js applications using Prisma, the connection() function from 'next/server' serves a different purpose than 'export const dynamic = force-dynamic'. The connection() function ensures proper database connection context and request isolation, while dynamic = 'force-dynamic' only controls rendering strategy. Both should be used together and are not redundant.

Applied to files:

  • orm/hono/src/lib/prisma.ts
📚 Learning: 2025-08-22T12:12:24.602Z
Learnt from: FGoessler
PR: prisma/prisma-examples#8260
File: generator-prisma-client/nextjs-starter-webpack-turborepo/packages/database/package.json:13-13
Timestamp: 2025-08-22T12:12:24.602Z
Learning: When analyzing changes from package.json seed scripts to "prisma db seed", always verify the actual content of prisma.config.ts rather than relying solely on regex patterns, as the configuration may be properly defined but in a format that doesn't match overly restrictive search patterns.

Applied to files:

  • orm/hono/prisma/seed.ts
🧬 Code graph analysis (2)
orm/hono/prisma/seed.ts (2)
orm/sveltekit/prisma/seed.ts (1)
  • main (39-43)
orm/express/prisma/seed.ts (1)
  • main (52-61)
orm/hono/src/index.ts (1)
orm/graphql/src/schema/post.ts (1)
  • PostCreateInput (18-23)
🪛 dotenv-linter (3.3.0)
orm/hono/.env.example

[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 markdownlint-cli2 (0.18.1)
orm/hono/README.md

11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


17-17: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


31-31: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


132-132: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: test (orm/betterauth-nextjs)
  • GitHub Check: test (orm/graphql-sdl-first)
  • GitHub Check: test (orm/express)
  • GitHub Check: test (orm/ai-sdk-nextjs)
  • GitHub Check: test (orm/grpc)
  • GitHub Check: test (orm/authjs-nextjs)
  • GitHub Check: test (orm/nest)
  • GitHub Check: test (orm/nest-graphql-sdl-first)
  • GitHub Check: test (orm/nest-graphql)
  • GitHub Check: test (orm/nuxt)
🔇 Additional comments (2)
orm/hono/prisma/schema.prisma (1)

1-35: LGTM! Schema is well-structured.

The Prisma schema is correctly configured with:

  • Custom client output path for better organization
  • Proper PostgreSQL datasource setup
  • Standard User/Post models with appropriate relations and constraints
orm/hono/tsconfig.json (1)

1-16: LGTM! TypeScript configuration is modern and appropriate.

The tsconfig properly sets up:

  • ESNext/NodeNext for modern module resolution
  • Strict type checking
  • Hono JSX runtime integration
  • Appropriate compiler options for the stack

Comment on lines +1 to +25
{
"name": "my-app",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@hono/node-server": "^1.19.5",
"@prisma/client": "^6.16.3",
"@prisma/extension-accelerate": "^2.0.2",
"dotenv": "^17.2.3",
"hono": "^4.9.9"
},
"devDependencies": {
"@types/node": "^20.11.17",
"prisma": "^6.16.3",
"tsx": "^4.20.6",
"typescript": "^5.8.3"
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

LGTM! Dependencies are current and properly configured.

The package configuration correctly sets up:

  • ESM module type for modern JavaScript
  • Appropriate build and dev scripts using tsx
  • Prisma seed script integration
  • Current stable versions of Hono, Prisma, and related packages

Optionally, consider updating Hono from 4.9.9 to 4.9.10 to get the latest fixes (based on learnings).

-    "hono": "^4.9.9"
+    "hono": "^4.9.10"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
"name": "my-app",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"dependencies": {
"@hono/node-server": "^1.19.5",
"@prisma/client": "^6.16.3",
"@prisma/extension-accelerate": "^2.0.2",
"dotenv": "^17.2.3",
"hono": "^4.9.9"
},
"devDependencies": {
"@types/node": "^20.11.17",
"prisma": "^6.16.3",
"tsx": "^4.20.6",
"typescript": "^5.8.3"
}
}
"dependencies": {
"@hono/node-server": "^1.19.5",
"@prisma/client": "^6.16.3",
"@prisma/extension-accelerate": "^2.0.2",
"dotenv": "^17.2.3",
"hono": "^4.9.10"
},
🤖 Prompt for AI Agents
In orm/hono/package.json around lines 1 to 25, update the hono dependency from
"^4.9.9" to "^4.9.10" to pick up the latest fixes; after editing the version run
your package manager (npm/yarn/pnpm install) to update lockfile and then run
build/test/seed scripts to verify nothing broke.

@mhartington mhartington merged commit 4c03bd1 into latest Oct 9, 2025
44 of 45 checks passed
@mhartington mhartington deleted the feat/hono branch October 9, 2025 22:25
@coderabbitai coderabbitai bot mentioned this pull request Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants