@bairock/lenz
v0.1.5
Published
GraphQL-MongoDB runtime: auto-generates input types, resolves @relation with DataLoader, converts $operators, generates TypeScript types
Readme
Lenz is a runtime engine that bridges GraphQL (Apollo Server) and MongoDB. Write your schema in GraphQL SDL — Lenz auto-generates input types with MongoDB operators, wraps resolvers, and provides a typed client that returns clean data instead of raw driver results.
import { LenzClient } from '@bairock/lenz';
const { schema, lenz } = await LenzClient({ mongo, typeDefs, resolvers });
// Fully typed — returns User, not InsertOneResult
const user = await lenz.collection('user').findOne({ name: { $eq: "Alice" } });Features
- Generates
FilterInput/InsertInput/UpdateInputfrom GraphQL SDL with MongoDB operators - Auto-converts
eq→$eq(GraphQL doesn't support$in field names) - Generates TypeScript types — models, filters, resolver arguments
- Typed client
lenz.collection('user').*with autocomplete - Response transformation:
_id→id,ObjectId→ string,Date→ ISO @relation— auto-resolves relationships via DataLoader (N+1 prevention), no$lookup— works on sharded clusters@unique— auto-creates unique indexes in MongoDB- Auto-sets
createdAt/updatedAt - Normalizes
MongoError→GraphQLErrorwith error codes
Installation
npm install @bairock/lenz @apollo/server graphql mongodbQuick Start
1. Schema (schema.graphql)
type User {
id: ID!
name: String!
email: String! @unique
createdAt: DateTime
updatedAt: DateTime
}
type Query {
findOneUser(filter: UserFilterInput!, options: FindOptionsInput): User
findManyUser(filter: UserFilterInput, sort: SortInput, skip: Int, limit: Int): [User!]!
countUser(filter: UserFilterInput): Int!
}
type Mutation {
insertOneUser(document: UserInsertInput!, options: FindOptionsInput): User!
updateOneUser(filter: UserFilterInput!, update: UserUpdateInput!, options: FindOptionsInput): User
deleteOneUser(filter: UserFilterInput!, options: FindOptionsInput): Boolean!
}2. Server
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { MongoClient } from 'mongodb';
import { LenzClient, gql } from '@bairock/lenz';
const mongo = await new MongoClient('mongodb://localhost:27017')
.connect().then(c => c.db('myapp'));
const typeDefs = gql`
type User { id: ID!, name: String!, email: String! @unique, createdAt: DateTime, updatedAt: DateTime }
type Query { findOneUser(filter: UserFilterInput!, options: FindOptionsInput): User ... }
type Mutation { insertOneUser(document: UserInsertInput!, options: FindOptionsInput): User! ... }
`;
const resolvers = {
Query: {
findOneUser: (_, args, { lenz }) => lenz.collection('user').findOne(args.filter, args.options),
findManyUser: (_, args, { lenz }) => lenz.collection('user').find(args.filter, args.options),
countUser: (_, args, { lenz }) => lenz.collection('user').countDocuments(args.filter),
},
Mutation: {
insertOneUser: (_, args, { lenz }) => lenz.collection('user').insertOne(args.document, args.options),
updateOneUser: (_, args, { lenz }) => lenz.collection('user').updateOne(args.filter, args.update, args.options),
deleteOneUser: (_, args, { lenz }) => lenz.collection('user').deleteOne(args.filter, args.options),
},
};
const { schema, lenz } = await LenzClient({ mongo, typeDefs, resolvers });
const server = new ApolloServer({ schema });
const { url } = await startStandaloneServer(server, {
context: async () => ({ lenz }),
listen: { port: 4000 },
});3. Generate TypeScript types
npx lenz generate typed --schema schema.graphql --out generated4. Generate CRUD modules (optional)
npx lenz generate crud --schema schema.graphql --out src/modules --tsClient
All methods return clean data, not raw MongoDB driver results.
lenz.collection('user').findOne(filter, options) → Promise<User | null>
lenz.collection('user').find(filter, options) → Promise<User[]>
lenz.collection('user').countDocuments(filter) → Promise<number>
lenz.collection('user').insertOne(document, options) → Promise<User>
lenz.collection('user').insertMany(documents) → Promise<number>
lenz.collection('user').updateOne(filter, update) → Promise<User | null>
lenz.collection('user').updateMany(filter, update) → Promise<number>
lenz.collection('user').replaceOne(filter, doc) → Promise<User | null>
lenz.collection('user').deleteOne(filter) → Promise<boolean>
lenz.collection('user').deleteMany(filter) → Promise<number>CLI
npx lenz init # create schema.graphql
npx lenz generate typed # generate TypeScript types
npx lenz generate crud # generate CRUD modulesOption types are re-exported from @bairock/lenz:
FindOneOptions, FindOptions, InsertOneOptions, UpdateOptions, ReplaceOptions, DeleteOptions, CountDocumentsOptions
Context
import type { Context } from '@bairock/lenz';
const server = new ApolloServer({ schema });
const { url } = await startStandaloneServer(server, {
context: async (): Promise<Context> => ({ lenz }),
});buildSchema()
Legacy helper — builds a transformed schema without the typed client:
import { buildSchema } from '@bairock/lenz';
const schema = buildSchema({ mongo, typeDefs, resolvers });Why Lenz
If you're building a GraphQL API on MongoDB, Lenz eliminates boilerplate:
- Manual input types — Lenz generates
FilterInput,InsertInput,UpdateInputfrom your SDL $operator conversion — GraphQL doesn't allow$in field names; Lenz convertseq→$eqautomatically- Raw driver results — Lenz unwraps
InsertOneResult,UpdateResult,DeleteResultand returns clean data - N+1 queries —
@relationuses DataLoader to batch-load related documents; no$lookupmeans all queries are point queries on_id, so relations work across shards withoutwhereclauses orallowDiskUse - Type conversions —
_id→id,ObjectId→ string,Date→ ISO — done automatically - Timestamps —
createdAt/updatedAtare set automatically - Indexes —
@uniquecreates MongoDB unique indexes on startup - Error handling —
MongoError→GraphQLErrorwith consistent error codes - TypeScript — Full type generation for models, input types, resolver args, and typed client delegates
Sharded clusters
MongoDB's $lookup does not work on sharded collections unless every shard contains a matching where clause, and even then it requires allowDiskUse and suffers from cross-shard network overhead. Lenz avoids $lookup entirely: @relation resolves references by collecting foreign _id values and loading them in a single batch via { _id: { $in: [...] } } — a point query that hits the correct shard directly. This makes Lenz a natural fit for sharded MongoDB deployments where $lookup-based ORMs break down.
Commands
npx lenz init — create schema.graphql
npx lenz generate typed — generate TypeScript types
npx lenz generate crud — generate CRUD modules