#organizing backend logic
1 messages · Page 1 of 1 (latest)
Hi @ripe coral. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
As your Convex backend grows, organizing your codebase for readability, scalability, and maintainability becomes crucial. Here are best practices and community patterns, directly from Convex documentation and experienced users:
1. Separate by Layers: "API Layer" and "DB Layer"
- DB Layer: Handles all direct data access, typically organized by table. For simple tables, use a single file; for complex tables, use subfolders (e.g.,
convex/db/users/read.ts,convex/db/users/write.ts). - API Layer: Organized by feature, this is where product development happens. It calls into the DB layer and handles business logic, validation, and authorization. This separation helps keep feature code fast to iterate on and ensures data integrity and authorization are handled consistently.
See Discord discussion for more details.
2. Use Helper Functions for Shared Logic
Write most of your logic as plain TypeScript functions (helpers) in a dedicated directory (e.g., convex/model or convex/helpers). Your public API functions (query, mutation, action) should be thin wrappers that call these helpers. This makes refactoring easier and keeps your API surface clean and focused
Best Practices: Use helper functions.
3. File/Folder Structure Patterns
Community members have shared these effective structures:
convex/
db/
users.ts
trades/
read.ts
write.ts
features/
notifications.ts
payments/
index.ts
helpers.ts
helpers/
userHelpers.ts
validation.ts
Or, by table:
convex/
users/
public.ts
private.ts
helpers.ts
actions.ts
schema.ts
trades/
...
This helps separate internal/private logic from public API, and keeps helpers reusable
See Discord thread for examples.
4. Keep Functions Short and Focused
Public API functions should mostly delegate to helpers. This keeps them readable and ensures business logic is centralized
Best Practices: Use helper functions.
5. Avoid Mixing Internal and Public Logic
Keep internal queries/mutations (no validation, used only by other backend code) separate from public ones (with validation/auth checks). This reduces mistakes and makes it clear which functions are safe to call from the client
See Discord thread for discussion.
6. Use Argument Validation and Logging
Always validate arguments in public functions and use logging for debugging. This helps catch errors early and keeps your backend secure
Best Practices: Functions.
By following these patterns, you’ll have a codebase that is easier to navigate, safer to extend, and more maintainable as your app grows. If you want more real-world examples, check out the ai-town Convex repo and join the Convex Discord for ongoing discussions.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch how can i make schema file not too long??
Let me check the knowledge base for you again. One moment! :books:
To keep your Convex schema file from getting too long, you can define and export field validators or schema fragments separately, then compose them in your main schema file. This lets you split out the details for each table or feature into their own files, making your schema modular and easier to maintain.
Example approach:
-
Define field validators in separate files:
// convex/recipesFields.ts import { v } from "convex/values"; export const recipeFields = { name: v.string(), course: v.union( v.literal("appetizer"), v.literal("main"), v.literal("dessert") ), ingredients: v.array(v.string()), steps: v.array(v.string()), }; -
Import and use them in your main schema:
// convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { recipeFields } from "./recipesFields"; // ...import other table field definitions export default defineSchema({ recipes: defineTable(recipeFields).index("by_course", ["course"]), // ...other tables });
This pattern keeps each table’s schema definition small and focused, and your main schema file just composes them together. It also makes it easier to reuse validators and keep your code DRY, as recommended in the Convex documentation and cookbooks
Types and Validators: A Convex Cookbook
Argument Validation without Repetition.
If you want to go further, you can group related tables or validators into feature folders, and import them into your schema file as needed. This modular approach is widely used in the Convex community to keep large codebases manageable.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch
how can i make this schema file not too long?
Let me check the knowledge base for you again. One moment! :books:
To keep your Convex schema file from getting too long, you can define and export field validators for each table in separate files, then import and compose them in your main schema file. This modular approach allows you to keep each table’s schema definition small and focused, making your main schema file much shorter and easier to maintain.
For example:
-
In a separate file, define the fields for a table:
// convex/recipeFields.ts import { v } from "convex/values"; export const recipeFields = { name: v.string(), course: v.union( v.literal("appetizer"), v.literal("main"), v.literal("dessert") ), ingredients: v.array(v.string()), steps: v.array(v.string()), }; -
In your main schema file, import and use these field definitions:
// convex/schema.ts import { defineSchema, defineTable } from "convex/server"; import { recipeFields } from "./recipeFields"; // import other table field definitions as needed export default defineSchema({ recipes: defineTable(recipeFields).index("by_course", ["course"]), // ...other tables });
This pattern is recommended in Convex’s official cookbooks and helps you avoid repetition and keep your schema maintainable as your app grows
Types and Validators: A Convex Cookbook
Argument Validation without Repetition.
If you need even more modularity, you can group related tables or validators into feature folders and import them as needed.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch alright, for organizing our app
convex/
db/
users.ts
trades/
read.ts
write.ts
features/
notifications.ts
payments/
index.ts
helpers.ts
helpers/
userHelpers.ts
validation.ts
with this apporach, can we make seperate folder inside features??
Let me check the knowledge base for you again. One moment! :books:
Yes, you can create separate folders inside the features directory to further organize your Convex backend code. This approach is commonly used by the Convex community to keep large codebases manageable and to group related logic by feature or domain.
For example, you might have:
convex/
features/
notifications/
index.ts
helpers.ts
payments/
index.ts
helpers.ts
loyalty/
orders.ts
rewards.ts
This structure allows you to keep feature-specific logic together, making your codebase more readable and maintainable as it grows. Community members and Convex staff have discussed and recommended organizing code by feature or concern, and using subfolders as needed for clarity and scalability. There are no restrictions from Convex on using folders in this way for your own code organization, as long as your API functions are exported correctly and you follow the naming conventions required by Convex for function discovery and code generation
Folder/File Naming/Structure Ideas
How do you guys scaffold your Convex backend projects?.
Just be aware that the folder structure will affect the API path (e.g., api.features.payments.index), so plan your organization accordingly. If you want to keep the API path flat, you may need to adjust how you export your functions.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch one thing i wanna know is what if one index file is getting so many queries and mutation? i had experienced that like my file became so long
Let me check the knowledge base for you again. One moment! :books:
If your index.ts file (or any single file) in a feature folder is getting too long because it contains many queries and mutations, the recommended approach is to split your logic into multiple files. This is a common practice in the Convex community and is supported by Convex’s function discovery system.
You can organize your feature folder like this:
convex/
features/
payments/
getPayments.ts
createPayment.ts
refundPayment.ts
helpers.ts
index.ts (optional, for barrel exports)
Each file can export a single query, mutation, or a small group of related functions. This keeps each file focused and manageable. You can also use a barrel index.ts file to re-export functions if you want to group them for import elsewhere.
This approach is discussed and recommended by Convex staff and community members for keeping codebases readable and scalable as your app grows. There are no restrictions from Convex on splitting functions into multiple files within a folder, and it is a best practice for large projects
Folder/File Naming/Structure Ideas
How do you guys scaffold your Convex backend projects?.
In summary: Split your queries and mutations into separate files within the feature folder to keep each file short and focused. This will make your codebase much easier to maintain as it grows.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch oh yes ... so like this can we design like this also ?
Or, by table:
convex/
users/
public.ts
private.ts
helpers.ts
actions.ts
schema.ts
trades/
...
Let me check the knowledge base for you again. One moment! :books:
Yes, you can organize your Convex backend code by table, using a folder structure like:
convex/
users/
public.ts
private.ts
helpers.ts
actions.ts
schema.ts
trades/
...
This approach is used by several community members and is discussed in Convex support threads. For example, one user shared a similar structure to separate internal and public queries/mutations, helpers, and schema files for each table. This helps keep related logic together and makes it easier to manage as your codebase grows. It’s especially useful when you have many tables or want to clearly separate internal logic from public API functions
How do you guys scaffold your Convex backend projects?.
There are no restrictions from Convex on organizing your code this way, as long as your exported functions follow Convex’s naming and export conventions. This pattern is considered a best practice for larger projects and is widely adopted in the Convex community.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch but with this approach what if one index file is getting so many queries and mutation? i had experienced that like my file became so long
Let me check the knowledge base for you again. One moment! :books:
If your index.ts (or any single file) for a table or feature is getting too long due to many queries and mutations, the recommended approach—supported by both Convex documentation and community practice—is to split your functions into multiple files within that folder.
For example, instead of putting all queries and mutations in users/index.ts, you can do:
convex/
users/
getUser.ts
listUsers.ts
createUser.ts
updateUser.ts
deleteUser.ts
helpers.ts
schema.ts
Each file contains a single query or mutation (or a small, related group), keeping files short and focused. This is a common pattern in the Convex community and is explicitly discussed as a way to keep codebases readable and maintainable as they grow
Folder/File Naming/Structure Ideas
How do you guys scaffold your Convex backend projects?.
Summary:
You do not need to keep all queries and mutations in a single file. Splitting them into separate files is encouraged and helps prevent any one file from becoming too large or unwieldy.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch alright, that's good now ... btw how should i organize http api organizely?
Let me check the knowledge base for you again. One moment! :books:
To organize your HTTP API in Convex, you can use the convex/http.ts file as the entry point and leverage routers and modularization for clarity and scalability.
Recommended practices:
-
Use the HttpRouter for Routing
- Define your HTTP routes in
convex/http.tsusinghttpRouterand theroutemethod. You can define each route inline or import handlers from other files. - Example:
import { httpRouter } from "convex/server"; import { postMessage, getByAuthor } from "./messages"; const http = httpRouter(); http.route({ path: "/postMessage", method: "POST", handler: postMessage, }); http.route({ path: "/getMessagesByAuthor", method: "GET", handler: getByAuthor, }); export default http; - You can also use
pathPrefixfor dynamic or grouped routes
Defining HTTP actions.
- Define your HTTP routes in
-
Split Handlers into Separate Files
- If your HTTP API grows, move each handler (e.g.,
postMessage,getByAuthor) into its own file or group them by feature. This keeps each file focused and easy to maintain.
- If your HTTP API grows, move each handler (e.g.,
-
Advanced Routing with Hono
- For more complex APIs (dynamic routes, middleware, etc.), you can integrate Hono with Convex. This allows you to define routes and middleware in a more expressive way, and then connect Hono to Convex’s HTTP actions.
- Example structure:
convex/ http.ts lib/ honoWithConvex.ts // other helpers features/ users/ httpHandlers.ts messages/ httpHandlers.ts - In
http.ts, you can register all routes with Hono and then connect it to Convex’s router
Advanced HTTP Endpoints: Convex ❤️ Hono.
-
Keep the Router File Focused
- The main
http.tsshould only register routes and import handlers. All business logic should live in separate handler files or feature folders.
- The main
-
Leverage Middleware and Logging
- If you use Hono, you can add middleware for logging, authentication, etc., to keep cross-cutting concerns out of your route handlers
Hono with Convex: Under the hood.
- If you use Hono, you can add middleware for logging, authentication, etc., to keep cross-cutting concerns out of your route handlers
Summary:
- Use
convex/http.tsas your router entry point. - Split HTTP handlers into separate files or feature folders.
- For advanced needs, use Hono for routing and middleware.
- Keep business logic out of the router file for clarity and maintainability.
This approach is scalable and aligns with both Convex documentation and community best practices.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch using covex hono vs regular
Let me check the knowledge base for you again. One moment! :books:
When deciding between using Convex's regular HTTP router (httpRouter) and Hono with Convex, here are the key differences and considerations based on the official documentation and community resources:
Regular Convex HTTP Router
- Simplicity: The built-in
httpRouteris straightforward and easy to use for basic HTTP endpoints. You define routes with exact paths or path prefixes and assign handlers to them. - Routing: Supports static routes and simple path prefixes (e.g.,
/api/feature/), but does not support dynamic route parameters (e.g.,/users/:userId) or advanced pattern matching. - Best for: Simple APIs, webhooks, or when you only need a few endpoints and don't require advanced routing or middleware.
- Example:
Defining HTTP actionsimport { httpRouter } from "convex/server"; import { myHandler } from "./handlers"; const http = httpRouter(); http.route({ path: "/my-endpoint", method: "GET", handler: myHandler }); export default http;
Hono with Convex
- Advanced Routing: Hono provides a more powerful and expressive router, supporting dynamic routes (e.g.,
/users/:userId), middleware, input validation, and more. - Middleware: Easily add CORS, authentication, logging, and other middleware to groups of routes or individual endpoints.
- Integration: You use
HttpRouterWithHonofrom theconvex-helperspackage to connect Hono to Convex. This allows you to leverage Hono's features while still deploying on Convex. - Best for: Complex APIs, when you need dynamic routes, middleware, or want to use familiar web patterns from frameworks like Express or Fastify.
- Example:
Advanced HTTP Endpoints: Convex ❤️ Honoimport { Hono } from "hono"; import { HonoWithConvex, HttpRouterWithHono } from "convex-helpers/server/hono"; import { ActionCtx } from "./_generated/server"; const app: HonoWithConvex<ActionCtx> = new Hono(); app.get("/users/:userId", async (c) => { /* ... */ }); export default new HttpRouterWithHono(app);
When to Use Which?
- Use the regular Convex router if your API is simple, you don't need dynamic routes, and you want minimal dependencies.
- Use Hono if you need dynamic routing, middleware, or want to structure your API in a more advanced or familiar way (similar to other web frameworks).
The Convex team and community recommend Hono for advanced use cases, especially when you need features like dynamic routes or middleware that the built-in router does not support. For most simple use cases, the built-in router is sufficient and easier to set up
Stack: Hono with Convex
Discord: Dynamic endpoints and router limitations.
If you outgrow the built-in router, you can migrate to Hono without changing your backend provider—Convex supports both approaches.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch how to make folder management for api
Let me check the knowledge base for you again. One moment! :books:
To manage your API folder structure in Convex, the recommended approach—based on community discussions and best practices—is to organize by feature or by table, and split your functions into multiple files or folders as your codebase grows. Here are some practical patterns:
1. Organize by Feature or Table
You can create a features/ or a db/ directory inside your convex/ folder, and then group related queries, mutations, and helpers together:
convex/
features/
notifications/
sendNotification.ts
listNotifications.ts
helpers.ts
payments/
createPayment.ts
refundPayment.ts
helpers.ts
db/
users/
getUser.ts
updateUser.ts
helpers.ts
trades/
...
This keeps related logic together and makes it easier to scale as your app grows
Folder/File Naming/Structure Ideas.
2. Split Large Files
If a single file (like index.ts) gets too long, split it into multiple files—one per query, mutation, or logical group. This is a common and recommended practice in the Convex community
How do you guys scaffold your Convex backend projects?.
3. Keep Internal and Public APIs Separate
Some teams use separate files for public and internal queries/mutations (e.g., public.ts, private.ts). This helps clarify which functions are safe to call from the client and which are for backend use only
How do you guys scaffold your Convex backend projects?.
4. HTTP API Organization
For HTTP endpoints, keep your main router in convex/http.ts and import handlers from feature folders. If you use Hono for advanced routing, you can further modularize your HTTP handlers by feature
Advanced HTTP Endpoints: Convex ❤️ Hono.
Summary:
- Group by feature or table.
- Split large files into smaller, focused files.
- Separate internal and public APIs.
- Modularize HTTP handlers and keep the router file clean.
This approach is scalable, maintainable, and aligns with Convex community best practices.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@ripe coral Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@fossil birch Do you recommend having index.ts file for barrel export purpose? This would make the syntax nice. However, how sure how would the performance be affected for the frontend side.
Let me check the knowledge base for you again. One moment! :books:
There’s no explicit recommendation in the docs or support threads about using index.ts barrel files in convex/, and there’s no information about frontend performance impact from doing so. So this is not officially documented either way.
What is clear from the sources:
- Convex discovers functions based on file path + export name inside
convex/. You’re free to structure files and folders as you like, as long as the actual Convex functions (query,mutation,action, etc.) are exported from modules underconvex/with valid paths. [Queries] - Helpers and shared logic are explicitly recommended to live in separate files and be imported where needed; these helper files can be in subfolders and are not special to Convex. [Best-practices functions; Use helper functions]
Given that:
- Having an
index.tsthat re-exports helpers (plain TS functions, types, etc.) for nicer imports is fully compatible with these patterns. - The knowledge sources don’t mention any negative impact on frontend performance from using barrel files. In typical TS/JS tooling, barrel files are resolved at build time by your bundler, not at runtime by Convex, but this behavior is not described in the Convex docs, so I can’t state it definitively.
One thing to be careful about (from the Discord threads):
- Codegen currently crawls all files under
convex/, which can create circular dependencies or heavier type-checking when helpers import from_generated/apiand vice versa. [Exclude folder from API generation]
Barrel files can contribute to this if they pull in a lot of modules, but the docs don’t quantify performance impact.
So, based on the available info:
- Using
index.tsfor barrel exports is allowed and fits with how people organize helpers and “db layer” modules. [Folder/file ideas; Scaffolding projects] - The knowledge sources do not say it will hurt frontend performance, nor do they warn against it.
- The main practical concern mentioned is TypeScript/codegen complexity and circular imports, not runtime performance.