MicroQL and GraphQL solve different problems despite both being query languages. Understanding these differences helps you choose the right tool for your use case.
MicroQL: Designed for service orchestration - composing and coordinating multiple service calls into workflows with automatic dependency resolution and parallel execution.
GraphQL: Designed for data selection - allowing clients to specify exactly which fields they want from a connected data graph.
MicroQL uses staged execution with automatic parallelization:
- Analyzes dependencies at compile-time
- Creates execution stages where independent operations run in parallel
- Three 10ms delays complete in ~12ms (proven by performance tests)
- 50 parallel operations with random delays complete in ~6ms
GraphQL uses field-by-field resolution:
- Resolvers execute in parent-child chains
- Parallelism requires manual optimization (DataLoader, etc.)
- Each field resolver decides how to fetch its data
MicroQL: Automatic - infers dependencies from $ and @ references
queries: {
profile: ['users:getProfile', {id: '$.given.userId'}],
auditLog: ['audit:log', {user: '$.profile'}] // Auto-detects dependency on profile
}GraphQL: Manual - developers write resolver logic
const resolvers = {
Query: {
auditLog: async (parent, args, context) => {
const profile = await getProfile(args.userId) // Manual orchestration
return logAccess(profile)
}
}
}// Service orchestration with dependency chains
const result = await query({
given: {userId: 'user123'},
services: {users, notifications, audit},
queries: {
// These run in parallel (no dependencies)
user: ['users:getProfile', {id: '$.given.userId'}],
preferences: ['users:getPreferences', {id: '$.given.userId'}],
// This waits for user data (has dependency)
notification: ['notifications:send', {
to: '$.user.email',
message: 'Welcome $.user.name!'
}],
// Sequential chain with @ context passing
pipeline: [
['data:extract', {source: '$.given.data'}],
['data:transform', {input: '@'}], // @ = previous step result
['data:validate', {processed: '@'}]
]
}
})# Field selection from connected graph
query {
user(id: "user123") {
id
name
email
posts {
title
comments {
text
author {
name
}
}
}
}
}Simple async functions focused on business logic:
const userService = {
async getProfile({id}) {
return await db.users.findById(id)
},
async updateProfile({id, updates}) {
await validateUpdates(updates)
return await db.users.update(id, updates)
}
}
// Optional: Add validation contracts
userService.updateProfile._validators = {
precheck: {
id: ['string', 'uuid'],
updates: {
name: ['string', 'optional'],
email: ['string', 'email', 'optional']
}
}
}Functions that resolve fields with context awareness:
const resolvers = {
Query: {
user: (parent, {id}, context, info) => {
return context.dataSources.users.findById(id)
}
},
User: {
posts: (user, args, context) => {
return context.dataSources.posts.findByUserId(user.id)
}
},
Post: {
author: (post, args, context) => {
return context.dataSources.users.findById(post.authorId)
}
}
}Parallel Execution: Independent queries run simultaneously
queries: {
user: ['users:get', {id: '$.userId'}],
orders: ['orders:getByUser', {userId: '$.userId'}],
preferences: ['prefs:get', {userId: '$.userId'}]
// All three execute in parallel
}Sequential Chains: Multi-step workflows with context passing
queries: {
pipeline: [
['parser:parseCSV', {data: '$.csvData'}],
['validator:check', {rows: '@'}], // @ = parsed data
['transformer:enrich', {data: '@'}], // @ = validated data
['storage:save', {records: '@'}] // @ = enriched data
]
}Method Syntax: Functional composition
queries: {
result: ['$.data', 'transform:filter', {service: ['data:isActive', {item: '@'}]}],
upper: ['$.result', 'transform:map', {service: ['string:toUpperCase', {text: '@'}]}]
}Nested Selection: Parent-child field resolution
query {
user(id: "123") { # Root resolver
name # Field resolver (may use default)
posts { # Field resolver gets user as parent
title # Field resolver gets post as parent
comments { # Field resolver gets post as parent
text # Field resolver gets comment as parent
}
}
}
}queries: {
user: ['users:getUser', {
id: '$.userId',
onError: ['fallback:getDefaultUser'], // Service-level fallback
retry: 3, // Built-in retry
timeout: 5000, // Built-in timeout
ignoreErrors: false // Continue on error
}]
}
// Global error handling
settings: {
onError: ['logger:logError'] // Global error handler
}const resolvers = {
Query: {
user: async (parent, {id}) => {
try {
const user = await userService.getById(id)
if (!user) {
throw new GraphQLError('User not found', {
extensions: { code: 'USER_NOT_FOUND' }
})
}
return user
} catch (error) {
// Handle specific errors, log, transform, etc.
throw new GraphQLError('Failed to fetch user')
}
}
}
}Automatic Parallelization:
- 3 independent 10ms operations complete in ~12ms
- 50 parallel operations complete in ~6ms
- Compile-time dependency analysis creates optimal execution plan
Minimal Overhead:
- Small codebase (~1000 lines)
- Minimal dependencies (lodash, zod)
- Direct function calls to services
Manual Optimization Required:
- N+1 query problems common without DataLoader
- Resolver-level parallelism needs careful design
- Query complexity analysis needed for DoS protection
Rich Ecosystem:
- Many optimization tools available
- Sophisticated caching strategies
- Advanced query analysis tools
- Service Orchestration: Composing multiple APIs/microservices
- Workflow Automation: Multi-step data processing pipelines
- Team Productivity: Enable non-technical team members to compose queries
- Performance Critical: Need automatic parallelization without manual optimization
- Operational Resilience: Want built-in retry, timeout, error recovery
- Simple Architecture: Prefer minimal dependencies and straightforward patterns
Example Use Cases:
- Data ETL pipelines
- API orchestration layers
- Business workflow automation
- Microservice coordination
- Integration platforms
- Client-Driven APIs: Clients need flexible field selection
- Graph-Like Data: Natural parent-child relationships in your data
- Fine-Grained Control: Need precise control over field resolution
- Rich Tooling: Want the extensive GraphQL ecosystem
- Industry Standards: Need widely-adopted API patterns
- Complex Schemas: Managing large, interconnected data graphs
Example Use Cases:
- Frontend APIs with flexible data requirements
- Mobile apps with bandwidth constraints
- Complex data relationships
- Multi-client APIs
- Real-time subscriptions
MicroQL:
// 8 lines - automatic dependency resolution
const result = await query({
given: {userId: 'user123'},
services: {users, audit},
queries: {
profile: ['users:getProfile', {id: '$.given.userId'}],
logged: ['audit:log', {action: 'profile_access', user: '$.profile'}]
}
})GraphQL:
// 25+ lines - manual resolver implementation
const typeDefs = `
type Query {
profile(id: ID!): User
}
type User {
id: ID!
name: String!
email: String!
}
`
const resolvers = {
Query: {
profile: async (parent, {id}, context) => {
const user = await context.dataSources.users.findById(id)
await context.dataSources.audit.log({
action: 'profile_access',
userId: id
})
return user
}
}
}
const server = new ApolloServer({typeDefs, resolvers})
const result = await server.executeOperation({
query: 'query { profile(id: "user123") { id name email } }'
})MicroQL excels at service orchestration with automatic dependency resolution, built-in parallelization, and operational resilience. It's ideal for teams building workflow automation, API composition, and data processing pipelines.
GraphQL excels at flexible data access with client-driven field selection and rich ecosystem tooling. It's ideal for teams building client-facing APIs with complex data relationships.
The choice depends on whether you're primarily composing services (MicroQL) or selecting data (GraphQL).