#Separate admin API from public?

10 messages · Page 1 of 1 (latest)

sick canyon
#

I have an application which has an admin interface which is restricted to users with the admin role in addition to a public app which includes anonymous and registered users.

I could approach this in two ways:

The more traditional unified API e.g.
api.posts.create (restricted by admin check)
api.posts.getById, { includeDraft: true}) // if includeDraft is true restrict to admins so public users can't see draft posts

or alternatively completely separate APIs:
api.admin.posts.create
api.admin.posts.getById // includes draft posts
api.public.posts.getById // excludes draft posts

The tradeoff of the separate APIs is that its more boilerplate in terms of having multiple endpoints that might have a degree of overlapping functionality. The benefit is that its clearer and access control can be easily enforced by wrapping all admin namespaced functions with ctx wrappers e.g. adminQuery, adminMutation.

I'm curious if their has been any discussion around patterns like these and recommendations or real life examples.

thin groveBOT
#

Hi @sick canyon. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Convex supports both unified and separated API patterns for handling admin and public endpoints, and there are tradeoffs to each approach. The knowledge sources discuss these patterns and provide guidance on structuring access control, but do not prescribe a single "best" way—rather, they highlight the importance of clear, enforceable authorization boundaries.

Unified API with Role Checks

A unified API (e.g., api.posts.create, api.posts.getById) is common and can be made secure by enforcing role-based access control within each endpoint. For example, you can check the user's role at the start of each function and throw an error if they lack the required permissions. This approach is flexible and avoids code duplication, but it requires discipline to ensure every endpoint has the correct checks:

export const createPost = mutation({
  args: { ... },
  handler: async (ctx, args) => {
    const user = await ensureUserAuthenticated(ctx);
    await ensureUserHasRoleOnTeam(ctx, user, args.teamId, "admin");
    // ...create post logic
  },
});

This pattern is discussed in the context of role-based access control, where you can parametrize custom functions to require a specific role for each endpoint, ensuring type safety and clarity at the call site. This makes it explicit which endpoints require admin privileges and helps prevent accidental exposure of sensitive operations to non-admins Role Based Access Control.

Separate Admin and Public APIs

#

Alternatively, you can split your API into separate namespaces, such as api.admin.posts.create and api.public.posts.getById. This makes the access boundaries very clear and allows you to wrap all admin endpoints with stricter middleware (e.g., adminQuery, adminMutation). The downside is increased boilerplate and potential duplication if the logic overlaps.

This pattern is not directly prescribed in the knowledge sources, but there are several discussions and code organization examples from the community that show similar approaches. For example, some users organize their code into separate files or folders for internal (admin) and public functions, which helps keep access control clear and maintainable. This is especially useful in larger projects where clarity and separation of concerns are important Discord: code organization.

Recommendations and Real-World Patterns

  • Defense in Depth: Regardless of structure, always enforce access control at the function boundary. Use role checks or middleware to ensure only authorized users can access sensitive endpoints Authorizing the endpoint.
  • Custom Middleware: Use custom function wrappers (e.g., adminMutation) to consolidate shared logic and reduce the risk of missing checks Role Based Access Control.
  • Code Organization: Community members often separate internal (admin) and public functions into different files or folders, which can help with clarity and maintainability, especially as your project grows Discord: code organization.

Summary

#

Both patterns are valid in Convex. The unified API is more DRY but requires careful, explicit checks. The separated API is clearer but can be more verbose. The key is to ensure robust, explicit access control at every endpoint, using middleware and code organization patterns that fit your team's needs and project scale.

If you want to see more real-world code organization examples, the community has shared several patterns in Discord threads, which may help you decide which approach best fits your project.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@sick canyon Kapa.ai is still learning and improving, please let me know how I did by reacting below.

sick canyon
#

@thin grove are there any good examples out there of real world style, decently large applications involving authorization and demonstrating code organization at scale with Convex?

thin groveBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

Yes, there are a few real-world examples and community discussions that showcase how larger Convex applications handle authorization and code organization:

  1. Application Portal by Web Development at Berkeley
    This project is a full-stack application portal built with Convex. The team shares their GitHub repo and describes their approach to schema design, query/mutation separation, and utility functions for authorization (e.g., getUser, isAdmin). Their codebase demonstrates how to structure logic for user roles and admin checks in a collaborative environment. You can see how they separate concerns and reuse authorization logic across endpoints Building an Application Portal on Convex.

  2. Community Code Organization Patterns
    In Discord discussions, several community members share their folder/file structures for organizing large Convex codebases. For example, one pattern is to separate each table or feature into its own folder, with files for public/internal queries, mutations, helpers, and schema. This helps manage complexity and makes it easier to enforce access control conventions. Trusted support staff also discuss separating an "API layer" (feature code) from a "DB layer" (data access), which is similar to controller-service-repository patterns in other frameworks. The main goal is to ensure authorization is dependable and data integrity is maintained as the codebase grows Discord: code organization.

#
  1. Role-Based Access Control Example
    The Convex Stack documentation provides concrete code samples for implementing role-based access control using a teamMembership table and helper functions to check user roles before allowing access to certain endpoints. This pattern is scalable and can be adapted to larger applications, especially when combined with custom function wrappers to enforce role checks consistently Role Based Access Control.

While there are not many massive open-source Convex apps yet, these resources and discussions provide practical, real-world patterns for organizing code and handling authorization at scale. If you want more references, the Convex Discord is active with developers sharing their approaches as their projects grow.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: