Skip to main content

Create an API endpoint

Marblism already generates all the basic API endpoints you would need for each entity in your database:

  • findMany
  • findUnique
  • create
  • update
  • delete

Marblism uses tRPC. You set up your routes on the server, and then you can call them directly from the front-end. It's super efficient, type-safe and makes the development process much simpler.

While ZenStack automatically handles CRUD operations for your data models, you can also create custom API endpoints using tRPC. This is particularly useful for implementing custom business logic, integrating external services, or creating specialized endpoints. Existing plugins such as AI, file upload are already defined following this pattern in the project.

Creating a Custom Router

Here's a basic example of how to create a custom router:

Create a new file in app/server/customRouter.ts

import { z } from 'zod'
import { Trpc } from '~/core/trpc/base'

export const CustomRouter = Trpc.createRouter({
greet: Trpc.procedure
.input(z.object({ name: z.string() }))
.mutation(async ({ input }) => {
return `Hello, ${input.name}!`
}),

randomNumber: Trpc.procedurePublic.mutation(() => {
return Math.random()
}),
})

In this example, we've created two endpoints:

  1. greet: Takes a name as input and returns a greeting. This endpoint is protected to authenticated users by default.
  2. randomNumber: Returns a random number. This endpoint is public and can be accessed without authentication.

Adding the Custom Router to the App Router

To make your custom router available, you need to add it to the main app router at /app/server/index.tsx:

import { createRouter } from '../../.marblism/zenstack/routers'
import { Trpc } from '../base'
import { CustomRouter } from './custom.router'

export const appRouter = Trpc.mergeRouters(
createRouter(Trpc.createRouter, Trpc.procedure),

// Add your custom router here
Trpc.createRouter({
custom: CustomRouter,
// ... other routers
}),
)

export type AppRouter = typeof appRouter

export const Server = {
appRouter,
}

Using the Custom Endpoint in the Frontend

Once your custom router is added to the app router, you can use it in your frontend components like this:

import { Api } from '@/core/trpc'

export default function HomePage() {
const { mutateAsync: generateRandomNumber } =
Api.custom.randomNumber.useMutation()

const handleGenerateRandomNumber = async () => {
try {
const randomNumber = await generateRandomNumber()

message.success(`Random number: ${randomNumber}`)
} catch (err) {
message.error('Failed to generate random number')
}
}

return (
<div>
<Button onClick={handleGenerateRandomNumber}>
Generate random number
</Button>
</div>
)
}

Access the database from a custom endpoint

The ctx object has access to the database:

  1. ctx.database (protected): This instance is enhanced with the user session using Zenstack. It provides access control based on the current user's permissions.

  2. ctx.databaseUnprotected: This is the raw Prisma client instance without any protection or enhancement.

Here is an example that count the number of likes of a specific tweet:

import { z } from 'zod'
import { Trpc } from '@/core/trpc/server'

export const CustomTweetRouter = Trpc.createRouter({
countLikesTweets: Trpc.procedure
.input(z.object({ tweetId: z.string() }))
.mutation(async ({ ctx, input }) => {
check()

const likes = await ctx.database.like.findFirst({
where: { tweetId: input.tweetId },
})

return likes.length
}),
})