

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
123456# Backend API Architecture and Development Guidelines78You are an expert TypeScript backend engineer specializing in building modern, type-safe APIs. Your expertise covers Hono for HTTP routing, Drizzle ORM for database operations, and React Query for frontend integration.910<core_architecture>1112<tech_stack>13- **Server & Routing**: Hono14- **Database ORM**: Drizzle with PostgreSQL15- **Frontend Integration**: React Query16- **Authentication**: Clerk17- **Validation**: Zod with zValidator18</tech_stack>1920<project_structure>21apps/22 ├── api/23 │ ├── src/24 │ │ ├── modules/ # Feature-based modules25 │ │ │ ├── [module]/ # e.g., posts, webhooks26 │ │ │ │ ├── [module].routes.ts # Route definitions27 │ │ │ │ └── [module].service.ts # Business logic & DB operations28 │ │ ├── pkg/ # Shared utilities and middleware29 │ │ └── index.ts # Main application setup30 └── web/31 └── src/32 └── api/33 └── [module].api.ts # React Query hooks3435packages/36 └── db/37 ├── src/38 │ ├── schema.ts # Database schema definitions39 │ ├── types.ts # Shared TypeScript types40 │ ├── index.ts # Main exports41 │ └── util/ # Database utilities42</project_structure>4344<module_development_guidelines>45### 1. Database layer46- If the request needs a new column or addition database fields start by creating and updating schema.ts47- Leverage types.ts inside packages/db to create new zod schemas for the newly created tables or columns and eg:48```49export type Post = InferSelectModel<typeof schema.posts>;50export type NewPost = InferInsertModel<typeof schema.posts>;5152export const postInsertSchema = createInsertSchema(schema.posts).omit({ userId: true });53export const postSelectSchema = createSelectSchema(schema.posts);54```555657### 2. Service Layer ([module].service.ts)58- Implement business logic59- Handle database operations using Drizzle60- Return strongly typed responses61- Keep services focused and modular62- Import db from the `packages/db`6364Example service implementation:65```ts66`[module].service.ts`67import { db, eq, items} from "@repo/db"6869export const moduleService = {70 async getItems() {71 return db.select().from(items);72 },7374 async createItem(data: NewItem) {75 return db.insert(items).values(data).returning();76 }77};78```798081### 3. Route Layer ([module].routes.ts)82- Define endpoints using Hono router83- Implement request validation using zValidator. You can leverage the created zod schemas from drizzle zod.84- Apply authentication middleware where needed85- Structure routes logically by resource86- **Use custom error classes instead of manual status responses** - Import from `@/pkg/errors` and throw appropriate errors87- **Avoid wrapping route handlers in try-catch blocks** - let error middleware handle exceptions88- Delegate all business logic and error handling to the service layer8990Example route implementation:91```ts92import { NotFoundError } from "@/pkg/errors";9394const moduleRoutes = new Hono()95 .use(auth(),requireAuth)96 .get("/", async (c) => {97 const items = await moduleService.getItems();98 return c.json(items);99 })100 .post("/", zValidator("json", insertSchema), async (c) => {101 const data = c.req.valid("json");102 const userId = getUserId(c);103 const result = await moduleService.createItem({ ...data, userId });104 // If results could be undefined or otherwise, throw proper error105 if (!result){106 throw new NotFoundError("Item not found");107 }108 return c.json(result);109 });110111112```113114After the route is created, you must add it to the `apps/api/src/index.ts` route so its accessable by the frontend.115116</module_development_guidelines>117118<frontend_integration>119When fetching data from the backend api on the client, use the following guidelines120`post.api.ts`121```ts122import { apiRpc, getApiClient, InferRequestType, callRpc } from "./client";123124const $createPost = apiRpc.posts.$post;125// Simple get126export async function getPosts() {127 const client = await getApiClient();128129 return callRpc(client.posts.$get());130}131// Safely leverage the typed params elsewhere within the nextjs application132export type CreatePostParams = InferRequestType<typeof $createPost>["json"];133export async function createPost(params: CreatePostParams) {134 const client = await getApiClient();135136 return callRpc(client.posts.$post({ json: params }));137}138```139</frontend_integration>140<package_management>141142- Use `pnpm` as the primary package manager for the project143- Install dependencies using `pnpm add [package-name]`144- Install dev dependencies using `pnpm add -D [package-name]`145- Install workspace dependencies using `pnpm add -w [package-name]`146</package_management>147148<development_guidelines>149### Running Scripts150- Use `bun` as the runtime environment and script runner151- Execute scripts defined in package.json using `bun run [script-name]`152- Run TypeScript files directly using `bun [file.ts]`153154### Monorepo155The project use turbo repo. To run everything, use the `turbo dev` script from the root folder.156157### Type Safety158- Use Drizzle schemas for database types159- Share types between frontend and backend using a shared package160- Leverage zod for runtime validation161162### Error Handling163- **Use custom error classes instead of manual JSON responses** - Import and throw errors from `@/pkg/errors/error`164- Available error classes: `NotFoundError`, `BadRequestError`, `UnauthorizedError`, `ForbiddenError`, `ConflictError`, `UnprocessableEntityError`, `InternalServerError`, `TooManyRequestsError`165- **NEVER return manual status responses** like `return c.json({error: "message"}, 401)` - always throw proper error classes166- Implement consistent error handlers across all routes167- Handle edge cases by throwing appropriate error classes168- Use the logger from the package @repo/logger with object inside, i.e `logger.error({...})` instead of `console.error`169170#### Error Class Usage Examples:171```ts172// ❌ DON'T DO THIS173if (!user) {174 return c.json({ error: "User not found" }, 404);175}176177// ✅ DO THIS INSTEAD178import { NotFoundError, BadRequestError, ConflictError } from "@/pkg/errors";179180if (!user) {181 throw new NotFoundError("User not found");182}183184if (email && await userExists(email)) {185 throw new ConflictError("User with this email already exists");186}187188if (!isValidInput(data)) {189 throw new BadRequestError("Invalid input data provided");190}191```192193### Authentication & Authorization194- Use Clerk middleware for authentication195- Implement role-based access control where needed196- Validate user permissions at the route level197- Keep authentication logic in middleware198199### API Design Principles200- Follow RESTful conventions201- Use consistent naming patterns202- Implement proper request validation203- Structure endpoints by resource/module204- Keep routes clean and delegate logic to services205206### Database Operations207- Use Drizzle for all database interactions208- Implement proper migrations209- Handle transactions when needed210- Write efficient queries211- Use appropriate indexes212</development_guidelines>213214<dev_workflow>215### Creating a New Module for different buisness logic2161. Create module directory in api/src/modules/[module]2172. Define routes in [module].routes.ts2183. Implement service logic in [module].service.ts2194. Add route to main application in index.ts2205. Create frontend integration in web/src/api/[module].api.ts221</dev_workflow>222223### Testing Requirements224- If needed use vitest to create testing files inside of the modules, [module].test.ts225-226### Code Quality227- For methods with more than one argument, use object destructuring: `function myMethod({ param1, param2 }: MyMethodParams) {...}`.228</best_practices>229230231<example_api_workflow>232Here's a complete example of a typical module:233234(if required) create new schemas.235// packages/db/schema.ts236.. new schema files237238Then:239```ts240// posts.service.ts241import { db, posts, type NewPost } from "@repo/db";242243export const postService = {244 async getPosts() {245 return db.select().from(posts);246 },247248 async createPost(post: NewPost) {249 return db.insert(posts).values(post).returning();250 }251};252253// posts.routes.ts254import { Hono } from "hono";255import { auth, requireAuth, getUserId } from "@/pkg/middleware/clerk-auth";256import { postService } from "./post.service";257import { zValidator } from "@/pkg/util/validator-wrapper";258import { postInsertSchema } from "@repo/db";259260export const postRoutes = new Hono()261 .use(auth(),requireAuth)262 .get("/", async (c) => {263 const posts = await postService.getPosts();264 return c.json(posts);265 })266 .post("/", zValidator("json", postInsertSchema), async (c) => {267 const data = c.req.valid("json");268 const userId = getUserId(c);269 const post = await postService.createPost({ ...data, userId });270 return c.json(post);271 });272```273274- Use the RPC-style API client for type-safe API calls275- Leverage `InferRequestType` for parameter typing276- Export plain async functions for API operations277- Optionally create React Query hooks when needed278```ts279// posts.api.ts (Frontend)280import { apiRpc, getApiClient, InferRequestType, callRpc } from "./client";281282const $createPost = apiRpc.posts.$post;283284export async function getPosts() {285 const client = await getApiClient();286 return callRpc(client.posts.$get());287}288289export async function createPost(params: InferRequestType<typeof $createPost>["json"]) {290 const client = await getApiClient();291 return callRpc(client.posts.$post({ json: params }));292 return response.json();293}294```295</example_api_workflow>296297298
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| sullyo/webapp-starter.cursor/rules/db.mdc · 870 | Cursor rules | no sections | 44/100 | today | |
| sullyo/webapp-starter.cursor/rules/frontend.mdc · 870 | Cursor rules | setuptestlint-formatstyle+7 | 91/100 | today |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 14 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 46 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 14 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 14 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 14 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 46 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/sullyo-webapp-starter-cursor-rules-api)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.