Skip to main content
Glama
api.ts10.1 kB
import { Hono } from "hono"; import { cors } from "hono/cors"; import { logger } from "hono/logger"; import { prettyJSON } from "hono/pretty-json"; import { secureHeaders } from "hono/secure-headers"; import { jwt, sign } from "hono/jwt"; import { validator } from "hono/validator"; import { HTTPException } from "hono/http-exception"; // ============================================ // Types // ============================================ interface User { id: number; email: string; name: string; createdAt: Date; } interface Post { id: number; title: string; content: string; authorId: number; published: boolean; createdAt: Date; } interface Env { JWT_SECRET: string; DATABASE_URL?: string; } interface Variables { user?: { id: number; email: string }; } // ============================================ // In-Memory Database // ============================================ class Database { private users = new Map<number, User & { passwordHash: string }>(); private posts = new Map<number, Post>(); private nextUserId = 1; private nextPostId = 1; createUser(email: string, name: string, password: string): User { const user = { id: this.nextUserId++, email, name, passwordHash: `hashed-${password}`, // Use bcrypt in production createdAt: new Date(), }; this.users.set(user.id, user); const { passwordHash, ...publicUser } = user; return publicUser; } getUser(id: number): 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(title: string, content: string, authorId: number): Post { const post: Post = { id: this.nextPostId++, title, content, authorId, published: false, createdAt: new Date(), }; this.posts.set(post.id, post); return post; } getPost(id: number): Post | undefined { return this.posts.get(id); } getPostsByAuthor(authorId: number): Post[] { return Array.from(this.posts.values()).filter( (p) => p.authorId === authorId, ); } getPublishedPosts(limit = 20, offset = 0): Post[] { return Array.from(this.posts.values()) .filter((p) => p.published) .slice(offset, offset + limit); } updatePost(id: number, data: Partial<Post>): Post | undefined { const post = this.posts.get(id); if (!post) return undefined; Object.assign(post, data); return post; } deletePost(id: number): boolean { return this.posts.delete(id); } } const db = new Database(); const JWT_SECRET = process.env.JWT_SECRET || "super-secret-key-change-in-production"; // ============================================ // App Setup // ============================================ const app = new Hono<{ Bindings: Env; Variables: Variables }>(); // Global Middleware app.use("*", logger()); app.use("*", prettyJSON()); app.use("*", secureHeaders()); app.use( "*", cors({ origin: ["http://localhost:3000", "https://example.com"], credentials: true, }), ); // Error Handler app.onError((err, c) => { if (err instanceof HTTPException) { return err.getResponse(); } console.error("Error:", err); return c.json({ error: "Internal Server Error" }, 500); }); // ============================================ // Public Routes // ============================================ app.get("/", (c) => { return c.json({ name: "Hono API Template", version: "1.0.0", endpoints: ["/auth", "/users", "/posts"], }); }); app.get("/health", (c) => { return c.json({ status: "healthy", timestamp: new Date().toISOString(), memory: process.memoryUsage?.() ?? "N/A", }); }); // ============================================ // Auth Routes // ============================================ const auth = new Hono<{ Bindings: Env; Variables: Variables }>(); auth.post( "/register", validator("json", (value, c) => { const { email, name, password } = value as any; if (!email || typeof email !== "string" || !email.includes("@")) { return c.json({ error: "Invalid email" }, 400); } if (!name || typeof name !== "string" || name.length < 2) { return c.json({ error: "Name must be at least 2 characters" }, 400); } if (!password || typeof password !== "string" || password.length < 8) { return c.json({ error: "Password must be at least 8 characters" }, 400); } return { email, name, password }; }), async (c) => { const { email, name, password } = c.req.valid("json"); if (db.getUserByEmail(email)) { throw new HTTPException(409, { message: "Email already exists" }); } const user = db.createUser(email, name, password); const token = await sign({ id: user.id, email: user.email }, JWT_SECRET); return c.json({ user, token }, 201); }, ); auth.post( "/login", validator("json", (value, c) => { const { email, password } = value as any; if (!email || !password) { return c.json({ error: "Email and password required" }, 400); } return { email, password }; }), async (c) => { const { email, password } = c.req.valid("json"); const user = db.getUserByEmail(email); if (!user || user.passwordHash !== `hashed-${password}`) { throw new HTTPException(401, { message: "Invalid credentials" }); } const { passwordHash, ...publicUser } = user; const token = await sign({ id: user.id, email: user.email }, JWT_SECRET); return c.json({ user: publicUser, token }); }, ); app.route("/auth", auth); // ============================================ // Protected Routes Middleware // ============================================ const protected_ = new Hono<{ Bindings: Env; Variables: Variables }>(); protected_.use("*", jwt({ secret: JWT_SECRET })); protected_.use("*", async (c, next) => { const payload = c.get("jwtPayload") as { id: number; email: string }; c.set("user", payload); await next(); }); // ============================================ // User Routes // ============================================ const users = new Hono<{ Bindings: Env; Variables: Variables }>(); users.get("/", (c) => { const users = db.getAllUsers(); return c.json({ users, count: users.length }); }); users.get("/me", (c) => { const user = c.get("user"); if (!user) throw new HTTPException(401, { message: "Not authenticated" }); const fullUser = db.getUser(user.id); return c.json(fullUser); }); users.get("/:id", (c) => { const id = parseInt(c.req.param("id")); const user = db.getUser(id); if (!user) throw new HTTPException(404, { message: "User not found" }); return c.json(user); }); protected_.route("/users", users); // ============================================ // Post Routes // ============================================ const posts = new Hono<{ Bindings: Env; Variables: Variables }>(); posts.get("/", (c) => { const limit = parseInt(c.req.query("limit") ?? "20"); const offset = parseInt(c.req.query("offset") ?? "0"); const posts = db.getPublishedPosts(limit, offset); return c.json({ posts, count: posts.length }); }); posts.get("/my", (c) => { const user = c.get("user"); if (!user) throw new HTTPException(401, { message: "Not authenticated" }); const posts = db.getPostsByAuthor(user.id); return c.json({ posts, count: posts.length }); }); posts.get("/:id", (c) => { const id = parseInt(c.req.param("id")); const post = db.getPost(id); if (!post) throw new HTTPException(404, { message: "Post not found" }); return c.json(post); }); posts.post( "/", validator("json", (value, c) => { const { title, content } = value as any; if (!title || typeof title !== "string" || title.length < 3) { return c.json({ error: "Title must be at least 3 characters" }, 400); } if (!content || typeof content !== "string") { return c.json({ error: "Content is required" }, 400); } return { title, content }; }), (c) => { const user = c.get("user"); if (!user) throw new HTTPException(401, { message: "Not authenticated" }); const { title, content } = c.req.valid("json"); const post = db.createPost(title, content, user.id); return c.json(post, 201); }, ); posts.patch("/:id/publish", (c) => { const user = c.get("user"); if (!user) throw new HTTPException(401, { message: "Not authenticated" }); const id = parseInt(c.req.param("id")); const post = db.getPost(id); if (!post) throw new HTTPException(404, { message: "Post not found" }); if (post.authorId !== user.id) throw new HTTPException(403, { message: "Not authorized" }); const updated = db.updatePost(id, { published: true }); return c.json(updated); }); posts.delete("/:id", (c) => { const user = c.get("user"); if (!user) throw new HTTPException(401, { message: "Not authenticated" }); const id = parseInt(c.req.param("id")); const post = db.getPost(id); if (!post) throw new HTTPException(404, { message: "Post not found" }); if (post.authorId !== user.id) throw new HTTPException(403, { message: "Not authorized" }); db.deletePost(id); return c.json({ deleted: true }, 200); }); protected_.route("/posts", posts); // Mount protected routes app.route("/api", protected_); // ============================================ // Export // ============================================ export default app; // For local development with Node.js // import { serve } from '@hono/node-server'; // serve({ fetch: app.fetch, port: 3000 }, (info) => { // console.log(`🚀 Hono Server: http://localhost:${info.port}`); // }); // For Cloudflare Workers (default export works) // For Bun: Bun.serve({ fetch: app.fetch, port: 3000 }); // For Deno: Deno.serve({ port: 3000 }, app.fetch); export { app, 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