
Product
Introducing Pull Request Stories to Help Security Teams Track Supply Chain Risks
Socketβs new Pull Request Stories give security teams clear visibility into dependency risks and outcomes across scanned pull requests.
espocrm-graphql-server
Advanced tools
A modern GraphQL server for EspoCRM with TypeScript, MCP integration, and AI-optimized dynamic schema generation
A high-performance, AI-optimized GraphQL server that dynamically generates schemas from EspoCRM metadata. Built specifically for modern AI agents and property management workflows.
The fastest way to get started:
# Start GraphQL server
npx espocrm-graphql-server
# Start MCP server for AI assistants
npx espocrm-mcp
# Initialize configuration files
npx espocrm-graphql-server init
# View help
npx espocrm-graphql-server --help
# Global installation
npm install -g espocrm-graphql-server
# Or use directly with npx (no installation required)
npx espocrm-graphql-server --version
git clone https://github.com/CG-Labs/EspoCRM-GraphQL-Server.git
cd EspoCRM-GraphQL-Server
npm install
cp .env.example .env
# Edit .env with your EspoCRM credentials
npm run dev
GraphQL Playground will be available at http://localhost:4000/graphql
# Install dependencies
npm install
# Setup database (if using direct MySQL)
npm run setup:db
# Generate initial schema
npm run schema:generate
# Start development server with hot reload
npm run dev
# Start with Docker Compose
docker-compose up -d
# View logs
docker-compose logs -f graphql
# Stop services
docker-compose down
# Build production image
docker build -f Dockerfile.prod -t espocrm-graphql:latest .
# Run with production compose
docker-compose -f docker-compose.prod.yml up -d
Create a .env
file based on .env.example
:
# EspoCRM Connection
ESPOCRM_API_URL=https://your-espocrm.com/api/v1
ESPOCRM_API_KEY=your_api_key_here
ESPOCRM_USERNAME=admin
ESPOCRM_PASSWORD=your_password
# Optional: Direct MySQL Connection (for performance)
MYSQL_HOST=your-mysql-host
MYSQL_PORT=3306
MYSQL_USER=espocrm_user
MYSQL_PASSWORD=your_mysql_password
MYSQL_DATABASE=espocrm_db
# Server Configuration
PORT=4000
NODE_ENV=production
LOG_LEVEL=info
# Security
JWT_SECRET=your-super-secret-jwt-key-here
API_RATE_LIMIT=100
API_RATE_WINDOW=15
# Redis Cache (Optional)
REDIS_URL=redis://localhost:6379
CACHE_TTL=300
# AI Optimization Features
AI_OPTIMIZATION_ENABLED=true
PERFORMANCE_MONITORING=true
AUTO_SCHEMA_REFRESH=true
SCHEMA_REFRESH_INTERVAL=3600
# Organization Specific
ORGANIZATION_NAME=Your Organization
DEFAULT_CURRENCY=GBP
PROPERTY_MANAGEMENT_MODE=true
COMPLIANCE_MODE=GDPR_UK
# Monitoring & Observability
ENABLE_METRICS=true
METRICS_PORT=9090
ENABLE_TRACING=false
JAEGER_ENDPOINT=http://localhost:14268/api/traces
# Development
GRAPHQL_PLAYGROUND=true
GRAPHQL_INTROSPECTION=true
DEBUG_MODE=false
For advanced configuration, edit config/default.json
:
{
"server": {
"port": 4000,
"cors": {
"origin": ["http://localhost:3000", "https://yourdomain.com"],
"credentials": true
}
},
"espocrm": {
"apiUrl": "https://your-espocrm.com/api/v1",
"timeout": 30000,
"retries": 3,
"rateLimit": {
"requests": 100,
"window": 60000
}
},
"cache": {
"type": "redis",
"ttl": 300,
"prefix": "espocrm:graphql:"
},
"ai": {
"optimizationEnabled": true,
"learningEnabled": true,
"performancePrediction": true
}
}
The GraphQL server supports multiple authentication methods to protect your data:
Configure API key authentication by setting the following environment variables:
GRAPHQL_REQUIRE_AUTH=true
GRAPHQL_API_KEYS=key1,key2,key3 # Comma-separated list
GRAPHQL_MASTER_KEY=master-key-for-admin
Include the API key in your requests:
# Using X-API-Key header
curl -X POST https://your-server/graphql \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"query": "{ contacts { edges { node { id } } } }"}'
# Using Authorization Bearer
curl -X POST https://your-server/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{"query": "{ contacts { edges { node { id } } } }"}'
Prevents abuse by limiting the number of requests:
RATE_LIMIT_MAX_REQUESTS=30 # Max requests per window
RATE_LIMIT_WINDOW_MS=60000 # Time window (1 minute)
Prevents malicious deeply nested queries:
GRAPHQL_DEPTH_LIMIT=10 # Maximum query depth
GRAPHQL_COMPLEXITY_LIMIT=1000 # Maximum complexity score
Control which origins can access your API:
CORS_ORIGIN=https://your-frontend.com # Specific origin
# or
CORS_ORIGIN=* # Allow all (not recommended for production)
GRAPHQL_REQUIRE_AUTH=true
ENABLE_INTROSPECTION=false
The server includes Helmet.js for security headers:
Endpoint | Authentication | Rate Limit | Description |
---|---|---|---|
/graphql | Optional/Required | Yes | Main GraphQL API |
/health | None | No | Health check |
/schema-info | Optional | Yes | Schema information |
/refresh-schema | Required | Strict | Manual schema refresh |
https://your-server.com:8000
Create Project:
EspoCRM GraphQL Server
AI-optimized GraphQL server for EspoCRM
Add Application:
https://github.com/CG-Labs/EspoCRM-GraphQL-Server.git
main
./Dockerfile.coolify
In Coolify dashboard, add these minimum required environment variables:
# Required Server Configuration
NODE_ENV=production
PORT=4000
LOG_LEVEL=info
# Optional: EspoCRM Configuration (server will run without these)
ESPOCRM_URL=https://your-espocrm.example.com # Note: Use ESPOCRM_URL not ESPOCRM_API_URL
ESPOCRM_API_KEY=your_api_key_here
# Optional: Database (for better performance)
MYSQL_HOST=your_mysql_host
MYSQL_USER=espocrm_user
MYSQL_PASSWORD=your_secure_password
MYSQL_DATABASE=espocrm_db
# Optional: Security
JWT_SECRET=generate_a_secure_random_string_here
API_RATE_LIMIT=100
# Optional: Redis (Coolify can provision this)
REDIS_URL=redis://redis:6379
# Optional: AI & Performance
AI_OPTIMIZATION_ENABLED=true
PERFORMANCE_MONITORING=true
AUTO_SCHEMA_REFRESH=true
# Optional: Organization Settings
ORGANIZATION_NAME=Your Organization
DEFAULT_CURRENCY=GBP
PROPERTY_MANAGEMENT_MODE=true
COMPLIANCE_MODE=GDPR_UK
Important Notes:
ESPOCRM_URL
(not ESPOCRM_API_URL
) for the base URL/api/v1
is added automaticallyCoolify will use the docker-compose.prod.yml
file:
version: '3.8'
services:
graphql:
image: node:18-alpine
working_dir: /app
command: sh -c "npm ci --only=production && npm start"
ports:
- "4000:4000"
environment:
- NODE_ENV=production
volumes:
- .:/app
depends_on:
- redis
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:4000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
restart: unless-stopped
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
volumes:
redis_data:
Deploy Application:
Configure Domain (Optional):
graphql.yourdomain.com
Verify Deployment:
https://graphql.yourdomain.com/graphql
https://graphql.yourdomain.com/health
# Add monitoring environment variables
ENABLE_METRICS=true
METRICS_PORT=9090
PROMETHEUS_ENDPOINT=/metrics
# In Coolify, configure automatic backups
BACKUP_ENABLED=true
BACKUP_SCHEDULE=0 2 * * * # Daily at 2 AM
BACKUP_RETENTION=7 # Keep 7 days
# Enable auto-scaling based on load
COOLIFY_SCALE_MIN=1
COOLIFY_SCALE_MAX=5
COOLIFY_CPU_LIMIT=1000m
COOLIFY_MEMORY_LIMIT=2Gi
The application includes comprehensive health checks:
# Health check endpoint
GET /health
# Response:
{
"status": "healthy",
"timestamp": "2024-01-01T00:00:00.000Z",
"uptime": 3600,
"version": "1.0.0",
"services": {
"espocrm": "connected",
"redis": "connected",
"database": "connected"
},
"performance": {
"memoryUsage": "125MB",
"cpuUsage": "15%",
"responseTime": "45ms"
}
}
Common Issues:
Environment Variables Not Loading:
# Check environment in Coolify logs
docker-compose logs graphql | grep ENV
Redis Connection Issues:
# Verify Redis service
docker-compose ps redis
docker-compose exec redis redis-cli ping
EspoCRM API Connection:
# Test API connectivity
curl -H "X-Api-Key: $ESPOCRM_API_KEY" $ESPOCRM_API_URL/App/user
Memory Issues:
# Increase memory limit in Coolify
COOLIFY_MEMORY_LIMIT=4Gi
espocrm-graphql/
βββ src/
β βββ core/ # Core GraphQL logic
β β βββ SchemaManager.js # Dynamic schema generation
β β βββ TypeGenerator.js # GraphQL type creation
β β βββ ResolverGenerator.js # Resolver generation
β β βββ AIOptimizer.js # AI-powered optimizations
β βββ clients/ # External service clients
β β βββ EspoCRMClient.js # EspoCRM API client
β βββ services/ # Business logic services
β βββ middleware/ # Express middleware
β βββ cache/ # Caching layer
β βββ utils/ # Utility functions
βββ tests/ # Test suites
βββ config/ # Configuration files
βββ docs/ # Documentation
βββ scripts/ # Deployment scripts
# Development with hot reload
npm run dev
# Type checking
npm run type-check
# Linting
npm run lint
npm run lint:fix
# Testing
npm run test # Run all tests
npm run test:unit # Unit tests only
npm run test:integration # Integration tests
npm run test:e2e # End-to-end tests
npm run test:coverage # Test coverage report
# Schema operations
npm run schema:generate # Generate fresh schema
npm run schema:validate # Validate current schema
npm run schema:introspect # Introspect EspoCRM metadata
# Performance testing
npm run test:performance # Performance benchmarks
npm run test:load # Load testing with Artillery
# Build and deployment
npm run build # Production build
npm run start # Start production server
npm run docker:build # Build Docker image
npm run docker:run # Run Docker container
Create Feature Branch:
git checkout -b feature/new-feature
Implement Feature:
src/
directoryTest Feature:
npm run test
npm run test:integration
npm run lint
Submit Pull Request:
The server dynamically generates GraphQL schemas based on your EspoCRM metadata. Here are some example queries:
# Get all contacts with pagination
query GetContacts($first: Int, $after: String) {
contacts(first: $first, after: $after) {
edges {
node {
id
name
emailAddress
phoneNumber
account {
name
website
}
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
# Property management specific query
query GetProperties($filter: PropertyFilter) {
properties(filter: $filter) {
edges {
node {
id
address
propertyType
rentAmount
currency
tenancies {
edges {
node {
id
status
startDate
endDate
tenant {
name
emailAddress
}
}
}
}
}
}
}
}
# AI-optimized bulk query
query GetDashboardData {
properties(first: 10) {
totalCount
edges {
node {
id
address
status
}
}
}
activeTenancies: tenancies(filter: { status: ACTIVE }) {
totalCount
}
maintenanceRequests(filter: { status: OPEN }) {
totalCount
edges {
node {
id
priority
description
property {
address
}
}
}
}
}
# Create a new property
mutation CreateProperty($input: PropertyInput!) {
createProperty(input: $input) {
id
address
propertyType
rentAmount
currency
status
}
}
# Update tenancy status
mutation UpdateTenancy($id: ID!, $input: TenancyUpdateInput!) {
updateTenancy(id: $id, input: $input) {
id
status
startDate
endDate
rentAmount
}
}
# Bulk create maintenance requests
mutation CreateMaintenanceRequests($inputs: [MaintenanceRequestInput!]!) {
createMaintenanceRequests(inputs: $inputs) {
id
priority
description
status
property {
address
}
}
}
# Real-time property updates
subscription PropertyUpdates($propertyId: ID!) {
propertyUpdated(id: $propertyId) {
id
status
lastUpdated
tenancy {
status
tenant {
name
}
}
}
}
# Maintenance request notifications
subscription MaintenanceAlerts {
maintenanceRequestCreated {
id
priority
description
property {
address
}
}
}
// Login to get JWT token
const response = await fetch('/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'user@example.com',
password: 'password'
})
});
const { token } = await response.json();
// Use token in GraphQL requests
const graphqlResponse = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
query: `query { contacts { edges { node { name } } } }`
})
});
// Use API key directly
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'your-api-key-here'
},
body: JSON.stringify({
query: `query { properties { totalCount } }`
})
});
The server implements multi-layer caching:
// Instead of N+1 queries
const contacts = await getContacts();
for (const contact of contacts) {
const account = await getAccount(contact.accountId); // N+1 problem
}
// Use DataLoader (automatic)
const contacts = await getContactsWithAccounts(); // Single optimized query
# Use cursor-based pagination for large datasets
query GetLargeDataset($first: Int = 50, $after: String) {
properties(first: $first, after: $after) {
edges {
cursor
node {
id
address
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Access performance metrics at /metrics
:
# GraphQL query performance
graphql_query_duration_seconds{operation="GetContacts"} 0.045
graphql_query_count{operation="GetContacts",status="success"} 1523
# Cache performance
cache_hit_rate{cache="schema"} 0.95
cache_miss_count{cache="query"} 145
# EspoCRM API performance
espocrm_api_duration_seconds{endpoint="/Contact"} 0.120
espocrm_api_error_count{endpoint="/Contact"} 3
The server includes built-in support for the Model Context Protocol (MCP), allowing AI assistants like Claude to directly interact with your CRM data.
MCP (Model Context Protocol) is an open protocol that enables AI assistants to connect with external data sources and tools in a secure, standardized way.
Configure Claude Desktop:
Edit your Claude Desktop config:
~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"espocrm": {
"command": "node",
"args": ["/path/to/espocrm-graphql-server/dist/mcp/index.js"],
"env": {
"MCP_GRAPHQL_ENDPOINT": "http://localhost:4000/graphql",
"MCP_API_KEY": "your-api-key"
}
}
}
}
Restart Claude Desktop to load the MCP server.
Deploy as an internet-accessible MCP server:
# Create Cloudflare Worker
npm create cloudflare@latest espocrm-mcp --template=cloudflare/ai/demos/remote-mcp-authless
# Deploy
wrangler deploy
For complete MCP documentation, see docs/MCP_INTEGRATION.md.
# Run all tests
npm test
# Run specific test suites
npm run test:unit # Unit tests
npm run test:integration # Integration tests
npm run test:e2e # End-to-end tests
npm run test:performance # Performance tests
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watch
tests/
βββ unit/ # Unit tests
β βββ core/ # Core component tests
β βββ services/ # Service tests
β βββ utils/ # Utility tests
βββ integration/ # Integration tests
β βββ espocrm/ # EspoCRM API tests
β βββ graphql/ # GraphQL API tests
βββ e2e/ # End-to-end tests
βββ performance/ # Performance benchmarks
βββ load/ # Load testing
βββ fixtures/ # Test data
// tests/unit/core/SchemaManager.test.js
import { SchemaManager } from '../../../src/core/SchemaManager.js';
describe('SchemaManager', () => {
let schemaManager;
beforeEach(() => {
schemaManager = new SchemaManager({
espocrm: { apiUrl: 'http://test.com' }
});
});
test('should generate schema from metadata', async () => {
const metadata = { entities: ['Contact', 'Account'] };
const schema = await schemaManager.generateSchema(metadata);
expect(schema).toBeDefined();
expect(schema.getType('Contact')).toBeDefined();
expect(schema.getType('Account')).toBeDefined();
});
});
// tests/integration/graphql/queries.test.js
import { createTestServer } from '../../utils/testServer.js';
describe('GraphQL Queries', () => {
let server;
beforeAll(async () => {
server = await createTestServer();
});
test('should fetch contacts with pagination', async () => {
const query = `
query {
contacts(first: 10) {
edges {
node {
id
name
}
}
pageInfo {
hasNextPage
}
}
}
`;
const response = await server.executeOperation({ query });
expect(response.errors).toBeUndefined();
expect(response.data.contacts.edges).toHaveLength(10);
});
});
# Run performance benchmarks
npm run test:performance
# Run load tests
npm run test:load
# Custom load test
artillery run tests/load/artillery.config.yml
The server provides comprehensive health checks:
# Basic health check
curl http://localhost:4000/health
# Detailed health check
curl http://localhost:4000/health/detailed
# Readiness check (for Kubernetes)
curl http://localhost:4000/ready
# Liveness check (for Kubernetes)
curl http://localhost:4000/live
Access metrics at http://localhost:4000/metrics
:
A pre-configured Grafana dashboard is available in monitoring/grafana/
:
Structured logging with Winston:
// Log levels: error, warn, info, debug, trace
logger.info('Schema generated successfully', {
entities: metadata.entities.length,
duration: Date.now() - startTime,
version: schemaVersion
});
logger.error('EspoCRM API error', {
endpoint: '/Contact',
statusCode: 500,
error: error.message,
requestId: req.id
});
We welcome contributions! Please see our Contributing Guide for details.
git checkout -b feature/amazing-feature
npm test
git commit -m 'Add amazing feature'
git push origin feature/amazing-feature
/docs
folder# Force schema refresh
curl -X POST http://localhost:4000/admin/refresh-schema
# Check schema cache
curl http://localhost:4000/admin/cache-stats
# Check metrics
curl http://localhost:4000/metrics
# Enable debug logging
export LOG_LEVEL=debug
npm restart
# Test API connectivity
curl -H "X-Api-Key: $ESPOCRM_API_KEY" $ESPOCRM_API_URL/App/user
# Check API quota
curl -H "X-Api-Key: $ESPOCRM_API_KEY" $ESPOCRM_API_URL/App/user/acl
See our detailed Troubleshooting Guide for common issues and solutions.
This project is licensed under the MIT License - see the LICENSE file for details.
Built with β€οΈ for the open source community
For more information, visit the GitHub repository.
FAQs
A modern GraphQL server for EspoCRM with TypeScript, MCP integration, and AI-optimized dynamic schema generation
We found that espocrm-graphql-server demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.Β It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socketβs new Pull Request Stories give security teams clear visibility into dependency risks and outcomes across scanned pull requests.
Research
/Security News
npm author Qixβs account was compromised, with malicious versions of popular packages like chalk-template, color-convert, and strip-ansi published.
Research
Four npm packages disguised as cryptographic tools steal developer credentials and send them to attacker-controlled Telegram infrastructure.