Skip to main content
Glama
server.ts9.63 kB
import { ApolloServer } from '@apollo/server'; import { expressMiddleware } from '@apollo/server/express4'; import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; import express from 'express'; import http from 'http'; import cors from 'cors'; // ============================================ // 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?: { id: string; email: string }; dataSources: DataSources; } interface DataSources { users: UserDataSource; posts: PostDataSource; } // ============================================ // In-Memory Data Sources // ============================================ class UserDataSource { private users = new Map<string, User>(); private nextId = 1; create(data: { email: string; name: string }): User { const user: User = { id: String(this.nextId++), ...data, createdAt: new Date(), }; this.users.set(user.id, user); return user; } getById(id: string): User | undefined { return this.users.get(id); } getByEmail(email: string): User | undefined { return Array.from(this.users.values()).find(u => u.email === email); } getAll(): User[] { return Array.from(this.users.values()); } update(id: string, data: Partial<User>): User | undefined { const user = this.users.get(id); if (!user) return undefined; const updated = { ...user, ...data }; this.users.set(id, updated); return updated; } delete(id: string): boolean { return this.users.delete(id); } } class PostDataSource { private posts = new Map<string, Post>(); private nextId = 1; create(data: { title: string; content: string; authorId: string }): Post { const post: Post = { id: String(this.nextId++), ...data, published: false, createdAt: new Date(), }; this.posts.set(post.id, post); return post; } getById(id: string): Post | undefined { return this.posts.get(id); } getByAuthor(authorId: string): Post[] { return Array.from(this.posts.values()).filter(p => p.authorId === authorId); } getPublished(limit = 20, offset = 0): Post[] { return Array.from(this.posts.values()) .filter(p => p.published) .slice(offset, offset + limit); } publish(id: string): Post | undefined { const post = this.posts.get(id); if (!post) return undefined; post.published = true; return post; } delete(id: string): boolean { return this.posts.delete(id); } } // ============================================ // GraphQL Schema // ============================================ const typeDefs = `#graphql scalar DateTime type User { id: ID! email: String! name: String! posts: [Post!]! createdAt: DateTime! } type Post { id: ID! title: String! content: String! author: User! published: Boolean! createdAt: DateTime! } type Query { # Users users: [User!]! user(id: ID!): User me: User # Posts posts(limit: Int, offset: Int): [Post!]! post(id: ID!): Post } type Mutation { # Auth register(email: String!, name: String!, password: String!): AuthPayload! login(email: String!, password: String!): AuthPayload! # Users updateProfile(name: String): User! deleteAccount: Boolean! # Posts createPost(title: String!, content: String!): Post! publishPost(id: ID!): Post deletePost(id: ID!): Boolean! } type AuthPayload { token: String! user: User! } type Subscription { postPublished: Post! } `; // ============================================ // Resolvers // ============================================ const resolvers = { DateTime: { __parseValue(value: number) { return new Date(value); }, __serialize(value: Date) { return value.toISOString(); }, }, Query: { users: (_: unknown, __: unknown, { dataSources }: Context) => { return dataSources.users.getAll(); }, user: (_: unknown, { id }: { id: string }, { dataSources }: Context) => { return dataSources.users.getById(id); }, me: (_: unknown, __: unknown, { user, dataSources }: Context) => { if (!user) throw new Error('Not authenticated'); return dataSources.users.getById(user.id); }, posts: (_: unknown, { limit, offset }: { limit?: number; offset?: number }, { dataSources }: Context) => { return dataSources.posts.getPublished(limit ?? 20, offset ?? 0); }, post: (_: unknown, { id }: { id: string }, { dataSources }: Context) => { return dataSources.posts.getById(id); }, }, Mutation: { register: (_: unknown, { email, name }: { email: string; name: string; password: string }, { dataSources }: Context) => { if (dataSources.users.getByEmail(email)) { throw new Error('Email already exists'); } const user = dataSources.users.create({ email, name }); return { token: `token-${user.id}`, user }; }, login: (_: unknown, { email }: { email: string; password: string }, { dataSources }: Context) => { const user = dataSources.users.getByEmail(email); if (!user) throw new Error('Invalid credentials'); // In production: verify password hash return { token: `token-${user.id}`, user }; }, updateProfile: (_: unknown, { name }: { name?: string }, { user, dataSources }: Context) => { if (!user) throw new Error('Not authenticated'); return dataSources.users.update(user.id, { name }); }, deleteAccount: (_: unknown, __: unknown, { user, dataSources }: Context) => { if (!user) throw new Error('Not authenticated'); return dataSources.users.delete(user.id); }, createPost: (_: unknown, { title, content }: { title: string; content: string }, { user, dataSources }: Context) => { if (!user) throw new Error('Not authenticated'); return dataSources.posts.create({ title, content, authorId: user.id }); }, publishPost: (_: unknown, { id }: { id: string }, { user, dataSources }: Context) => { if (!user) throw new Error('Not authenticated'); const post = dataSources.posts.getById(id); if (!post || post.authorId !== user.id) throw new Error('Not authorized'); return dataSources.posts.publish(id); }, deletePost: (_: unknown, { id }: { id: string }, { user, dataSources }: Context) => { if (!user) throw new Error('Not authenticated'); const post = dataSources.posts.getById(id); if (!post || post.authorId !== user.id) throw new Error('Not authorized'); return dataSources.posts.delete(id); }, }, User: { posts: (parent: User, _: unknown, { dataSources }: Context) => { return dataSources.posts.getByAuthor(parent.id); }, }, Post: { author: (parent: Post, _: unknown, { dataSources }: Context) => { return dataSources.users.getById(parent.authorId); }, }, }; // ============================================ // Auth Helper // ============================================ function extractUser(token?: string): { id: string; email: string } | undefined { if (!token?.startsWith('Bearer ')) return undefined; const jwt = token.slice(7); // In production: verify JWT properly if (jwt.startsWith('token-')) { return { id: jwt.replace('token-', ''), email: 'user@example.com' }; } return undefined; } // ============================================ // Server Setup // ============================================ const userDS = new UserDataSource(); const postDS = new PostDataSource(); async function startServer() { const app = express(); const httpServer = http.createServer(app); const server = new ApolloServer<Context>({ typeDefs, resolvers: resolvers as any, plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], introspection: process.env.NODE_ENV !== 'production', }); await server.start(); app.use( '/graphql', cors<cors.CorsRequest>(), express.json(), expressMiddleware(server, { context: async ({ req }) => ({ user: extractUser(req.headers.authorization), dataSources: { users: userDS, posts: postDS, }, }), }) ); // Health check app.get('/health', (_, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }); }); const PORT = process.env.PORT || 4000; httpServer.listen(PORT, () => { console.log(`🚀 GraphQL Server: http://localhost:${PORT}/graphql`); }); } startServer().catch(console.error); export { typeDefs, resolvers, UserDataSource, PostDataSource };

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