258 lines
6.9 KiB
Markdown
258 lines
6.9 KiB
Markdown
# tRPC Implementation Documentation
|
|
|
|
## Overview
|
|
|
|
This project implements a [tRPC](https://trpc.io/) 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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
// 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/`:
|
|
|
|
```typescript
|
|
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`:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// In your frontend component
|
|
const data = await api.myRouter.hello.query("World");
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Type Safety**: Always use Zod schemas to validate input data and return types.
|
|
|
|
2. **Error Handling**: Implement proper error handling with try/catch blocks in async procedures.
|
|
|
|
3. **Procedure Organization**: Group related procedures into logical routers.
|
|
|
|
4. **Consistent Naming**: Use clear, descriptive names for your procedures and routers.
|
|
|
|
5. **Documentation**: Document each procedure with clear descriptions of what it does.
|
|
|
|
## Example Usage Patterns
|
|
|
|
### Query Procedure (GET)
|
|
```typescript
|
|
// 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 };
|
|
})
|
|
```
|
|
|
|
```typescript
|
|
// In frontend component
|
|
const { data, isLoading } = api.database.getPosts.useQuery({ limit: 10 });
|
|
```
|
|
|
|
### Mutation Procedure (POST/PUT/DELETE)
|
|
```typescript
|
|
// 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 } };
|
|
})
|
|
```
|
|
|
|
```typescript
|
|
// 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 callback
|
|
- `auth.googleCallback` - Google OAuth callback
|
|
- `auth.emailLogin` - Email login
|
|
- `auth.emailVerification` - Email verification
|
|
|
|
### Database
|
|
- `database.getCommentReactions` - Get comment reactions
|
|
- `database.postCommentReaction` - Add comment reaction
|
|
- `database.deleteCommentReaction` - Remove comment reaction
|
|
- `database.getComments` - Get comments for a post
|
|
- `database.getPosts` - Get posts with pagination
|
|
- `database.createPost` - Create new post
|
|
- `database.updatePost` - Update existing post
|
|
- `database.deletePost` - Delete post
|
|
- `database.getPostLikes` - Get likes for a post
|
|
- `database.likePost` - Like a post
|
|
- `database.unlikePost` - Unlike a post
|
|
|
|
### Lineage
|
|
- `lineage.databaseManagement` - Database management operations
|
|
- `lineage.analytics` - Analytics endpoints
|
|
- `lineage.appleAuth` - Apple authentication
|
|
- `lineage.emailLogin` - Email login
|
|
- `lineage.emailRegister` - Email registration
|
|
- `lineage.emailVerify` - Email verification
|
|
- `lineage.googleRegister` - Google registration
|
|
- `lineage.attacks` - Attack data
|
|
- `lineage.conditions` - Condition data
|
|
- `lineage.dungeons` - Dungeon data
|
|
- `lineage.enemies` - Enemy data
|
|
- `lineage.items` - Item data
|
|
- `lineage.misc` - Miscellaneous data
|
|
- `lineage.offlineSecret` - Offline secret endpoint
|
|
- `lineage.pvpGet` - PvP GET operations
|
|
- `lineage.pvpPost` - PvP POST operations
|
|
- `lineage.tokens` - Token operations
|
|
|
|
### Misc
|
|
- `misc.downloads` - Downloads endpoint
|
|
- `misc.s3Delete` - Delete S3 object
|
|
- `misc.s3Get` - Get S3 object
|
|
- `misc.hashPassword` - Hash password |