Skip to main content
Glama
router.md8.41 kB
```typescript import { initTRPC, TRPCError } from "@trpc/server"; import { createHTTPServer } from "@trpc/server/adapters/standalone"; import { z } from "zod"; // ============================================ // Types // ============================================ interface User { id: string; email: string; name: string; createdAt: Date; } interface Post { id: string; title: string; content: string; authorId: string; published: boolean; createdAt: Date; } interface Context { user: User | null; } // ============================================ // In-Memory Database // ============================================ class Database { users = new Map<string, User & { passwordHash: string }>(); posts = new Map<string, Post>(); private nextUserId = 1; private nextPostId = 1; createUser(data: { email: string; name: string; password: string }): User { const user = { id: String(this.nextUserId++), email: data.email, name: data.name, passwordHash: `hashed-${data.password}`, // Use bcrypt in production createdAt: new Date(), }; this.users.set(user.id, user); const { passwordHash, ...publicUser } = user; return publicUser; } getUser(id: string): User | undefined { const user = this.users.get(id); if (!user) return undefined; const { passwordHash, ...publicUser } = user; return publicUser; } getUserByEmail(email: string): (User & { passwordHash: string }) | undefined { return Array.from(this.users.values()).find((u) => u.email === email); } getAllUsers(): User[] { return Array.from(this.users.values()).map(({ passwordHash, ...u }) => u); } createPost(data: { title: string; content: string; authorId: string }): Post { const post: Post = { id: String(this.nextPostId++), ...data, published: false, createdAt: new Date(), }; this.posts.set(post.id, post); return post; } getPost(id: string): Post | undefined { return this.posts.get(id); } getPostsByAuthor(authorId: string): Post[] { return Array.from(this.posts.values()).filter( (p) => p.authorId === authorId, ); } getPublishedPosts(): Post[] { return Array.from(this.posts.values()).filter((p) => p.published); } updatePost(id: string, data: Partial<Post>): Post | undefined { const post = this.posts.get(id); if (!post) return undefined; Object.assign(post, data); return post; } deletePost(id: string): boolean { return this.posts.delete(id); } } const db = new Database(); // ============================================ // tRPC Setup // ============================================ const t = initTRPC.context<Context>().create(); const publicProcedure = t.procedure; const protectedProcedure = t.procedure.use(async ({ ctx, next }) => { if (!ctx.user) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Must be logged in" }); } return next({ ctx: { ...ctx, user: ctx.user } }); }); // ============================================ // Input Schemas // ============================================ const registerSchema = z.object({ email: z.string().email(), name: z.string().min(2).max(50), password: z.string().min(8), }); const loginSchema = z.object({ email: z.string().email(), password: z.string(), }); const createPostSchema = z.object({ title: z.string().min(3).max(200), content: z.string().min(10), }); const updatePostSchema = z.object({ id: z.string(), title: z.string().min(3).max(200).optional(), content: z.string().min(10).optional(), }); const paginationSchema = z.object({ limit: z.number().min(1).max(100).default(20), cursor: z.string().optional(), }); // ============================================ // User Router // ============================================ const userRouter = t.router({ register: publicProcedure .input(registerSchema) .mutation(async ({ input }) => { if (db.getUserByEmail(input.email)) { throw new TRPCError({ code: "CONFLICT", message: "Email already exists", }); } const user = db.createUser(input); return { user, token: `token-${user.id}` }; }), login: publicProcedure.input(loginSchema).mutation(async ({ input }) => { const user = db.getUserByEmail(input.email); if (!user || user.passwordHash !== `hashed-${input.password}`) { throw new TRPCError({ code: "UNAUTHORIZED", message: "Invalid credentials", }); } const { passwordHash, ...publicUser } = user; return { user: publicUser, token: `token-${user.id}` }; }), me: protectedProcedure.query(({ ctx }) => ctx.user), list: publicProcedure.query(() => db.getAllUsers()), getById: publicProcedure .input(z.object({ id: z.string() })) .query(({ input }) => { const user = db.getUser(input.id); if (!user) throw new TRPCError({ code: "NOT_FOUND", message: "User not found" }); return user; }), }); // ============================================ // Post Router // ============================================ const postRouter = t.router({ list: publicProcedure.input(paginationSchema).query(({ input }) => { const posts = db.getPublishedPosts(); return { items: posts.slice(0, input.limit), nextCursor: posts.length > input.limit ? posts[input.limit].id : undefined, }; }), getById: publicProcedure .input(z.object({ id: z.string() })) .query(({ input }) => { const post = db.getPost(input.id); if (!post) throw new TRPCError({ code: "NOT_FOUND", message: "Post not found" }); return post; }), myPosts: protectedProcedure.query(({ ctx }) => db.getPostsByAuthor(ctx.user.id), ), create: protectedProcedure .input(createPostSchema) .mutation(({ ctx, input }) => { return db.createPost({ ...input, authorId: ctx.user.id }); }), update: protectedProcedure .input(updatePostSchema) .mutation(({ ctx, input }) => { const post = db.getPost(input.id); if (!post) throw new TRPCError({ code: "NOT_FOUND", message: "Post not found" }); if (post.authorId !== ctx.user.id) { throw new TRPCError({ code: "FORBIDDEN", message: "Not your post" }); } return db.updatePost(input.id, input); }), publish: protectedProcedure .input(z.object({ id: z.string() })) .mutation(({ ctx, input }) => { const post = db.getPost(input.id); if (!post) throw new TRPCError({ code: "NOT_FOUND", message: "Post not found" }); if (post.authorId !== ctx.user.id) { throw new TRPCError({ code: "FORBIDDEN", message: "Not your post" }); } return db.updatePost(input.id, { published: true }); }), delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(({ ctx, input }) => { const post = db.getPost(input.id); if (!post) throw new TRPCError({ code: "NOT_FOUND", message: "Post not found" }); if (post.authorId !== ctx.user.id) { throw new TRPCError({ code: "FORBIDDEN", message: "Not your post" }); } return db.deletePost(input.id); }), }); // ============================================ // App Router // ============================================ const appRouter = t.router({ health: publicProcedure.query(() => ({ status: "healthy", timestamp: new Date().toISOString(), })), user: userRouter, post: postRouter, }); export type AppRouter = typeof appRouter; // ============================================ // Context Creation // ============================================ function createContext(opts: { req: { headers: { authorization?: string } }; }): Context { const token = opts.req.headers.authorization?.replace("Bearer ", ""); if (token?.startsWith("token-")) { const userId = token.replace("token-", ""); const user = db.getUser(userId); return { user: user ?? null }; } return { user: null }; } // ============================================ // Server // ============================================ const server = createHTTPServer({ router: appRouter, createContext, }); const PORT = process.env.PORT || 3000; server.listen(PORT); console.log(`🚀 tRPC Server: http://localhost:${PORT}`); export { appRouter, createContext, db }; ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/millsydotdev/Code-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server