6.9 KiB
tRPC Implementation Documentation
Overview
This project implements a tRPC API layer to provide type-safe communication between the frontend and backend. The implementation follows SolidStart's server-side rendering architecture with a clear separation of concerns.
Architecture
The tRPC setup is organized in the following structure:
src/
├── server/
│ └── api/
│ ├── root.ts # Main router that combines all sub-routers
│ ├── utils.ts # tRPC utility functions and initialization
│ └── routers/ # Individual API route groups
│ ├── auth.ts # Authentication procedures
│ ├── database.ts # Database operations
│ ├── example.ts # Example procedures
│ ├── lineage.ts # Lineage-related APIs
│ └── misc.ts # Miscellaneous endpoints
└── routes/
└── api/
└── trpc/
└── [trpc].ts # API endpoint handler
How to Use tRPC Procedures from the Frontend
The api client is pre-configured and available for use in components:
import { api } from "~/lib/api";
// Example usage in a component
export function MyComponent() {
const [result, setResult] = useState<string | null>(null);
const handleClick = async () => {
try {
// Call a tRPC procedure
const data = await api.example.hello.query("World");
setResult(data);
} catch (error) {
console.error("Error calling tRPC procedure:", error);
}
};
return (
<div>
<p>{result}</p>
<button onClick={handleClick}>Call API</button>
</div>
);
}
API Route Structure
Root Router (src/server/api/root.ts)
The main router combines all individual routers:
export const appRouter = createTRPCRouter({
example: exampleRouter,
auth: authRouter,
database: databaseRouter,
lineage: lineageRouter,
misc: miscRouter
});
Procedure Types
tRPC provides two main procedure types:
- Query: For read-only operations (GET requests)
- Mutation: For write operations (POST, PUT, DELETE requests)
Example:
// Query procedure - read-only
publicProcedure
.input(z.string())
.query(({ input }) => {
return `Hello ${input}!`;
})
// Mutation procedure - write operation
publicProcedure
.input(z.object({ name: z.string() }))
.mutation(({ input }) => {
// Logic for creating/updating data
return { success: true, name: input.name };
})
Adding New Endpoints
1. Create a new router file
Create a new file in src/server/api/routers/:
import { createTRPCRouter, publicProcedure } from "../utils";
import { z } from "zod";
export const myRouter = createTRPCRouter({
// Add your procedures here
hello: publicProcedure
.input(z.string())
.query(({ input }) => {
return `Hello ${input}!`;
}),
});
2. Register the router in the root
Add your new router to src/server/api/root.ts:
import { exampleRouter } from "./routers/example";
import { authRouter } from "./routers/auth";
import { databaseRouter } from "./routers/database";
import { lineageRouter } from "./routers/lineage";
import { miscRouter } from "./routers/misc";
import { myRouter } from "./routers/myRouter"; // Add this import
import { createTRPCRouter } from "./utils";
export const appRouter = createTRPCRouter({
example: exampleRouter,
auth: authRouter,
database: databaseRouter,
lineage: lineageRouter,
misc: miscRouter,
myRouter: myRouter, // Add this line
});
3. Use in frontend
// In your frontend component
const data = await api.myRouter.hello.query("World");
Best Practices
-
Type Safety: Always use Zod schemas to validate input data and return types.
-
Error Handling: Implement proper error handling with try/catch blocks in async procedures.
-
Procedure Organization: Group related procedures into logical routers.
-
Consistent Naming: Use clear, descriptive names for your procedures and routers.
-
Documentation: Document each procedure with clear descriptions of what it does.
Example Usage Patterns
Query Procedure (GET)
// In your router file
getPosts: publicProcedure
.input(z.object({
limit: z.number().optional(),
offset: z.number().optional()
}))
.query(({ input }) => {
// Return data from database or external service
return { posts: [], total: 0 };
})
// In frontend component
const { data, isLoading } = api.database.getPosts.useQuery({ limit: 10 });
Mutation Procedure (POST/PUT/DELETE)
// In your router file
createPost: publicProcedure
.input(z.object({
title: z.string(),
content: z.string()
}))
.mutation(({ input }) => {
// Create post in database
return { success: true, post: { id: "1", ...input } };
})
// In frontend component
const { mutateAsync } = api.database.createPost.useMutation();
const handleClick = async () => {
try {
const result = await mutateAsync({
title: "New Post",
content: "Post content"
});
console.log("Created post:", result);
} catch (error) {
console.error("Error creating post:", error);
}
};
Available Endpoints
Auth
auth.githubCallback- GitHub OAuth callbackauth.googleCallback- Google OAuth callbackauth.emailLogin- Email loginauth.emailVerification- Email verification
Database
database.getCommentReactions- Get comment reactionsdatabase.postCommentReaction- Add comment reactiondatabase.deleteCommentReaction- Remove comment reactiondatabase.getComments- Get comments for a postdatabase.getPosts- Get posts with paginationdatabase.createPost- Create new postdatabase.updatePost- Update existing postdatabase.deletePost- Delete postdatabase.getPostLikes- Get likes for a postdatabase.likePost- Like a postdatabase.unlikePost- Unlike a post
Lineage
lineage.databaseManagement- Database management operationslineage.analytics- Analytics endpointslineage.appleAuth- Apple authenticationlineage.emailLogin- Email loginlineage.emailRegister- Email registrationlineage.emailVerify- Email verificationlineage.googleRegister- Google registrationlineage.attacks- Attack datalineage.conditions- Condition datalineage.dungeons- Dungeon datalineage.enemies- Enemy datalineage.items- Item datalineage.misc- Miscellaneous datalineage.offlineSecret- Offline secret endpointlineage.pvpGet- PvP GET operationslineage.pvpPost- PvP POST operationslineage.tokens- Token operations
Misc
misc.downloads- Downloads endpointmisc.s3Delete- Delete S3 objectmisc.s3Get- Get S3 objectmisc.hashPassword- Hash password