@quell/server v10.0.0
@quell/server
@quell/server is an easy-to-implement Node.js/Express middleware that satisfies and caches GraphQL queries and mutations. Quell's schema-governed, type-level normalization algorithm caches GraphQL query and mutation responses as flattened key-value representations of the graph's nodes, making it possible to partially satisfy queries from the server's Redis cache, reformulate the query, and then fetch additional data from other APIs or databases.
Table of Contents
- Installation
- Quick Setup with CLI
- Implementation Guide
- Rate and Cost Limiting Implementation
- Schema Compatibility Layer
- Usage Notes
- Contributors
- Related Documentation
@quell/server is an open-source NPM package accelerated by OS Labs and developed by Cassidy Komp, Andrew Dai, Stacey Lee, Ian Weinholtz, Angelo Chengcuenca, Emily Hoang, Keely Timms, Yusuf Bhaiyat, Chang Cai, Robert Howton, Joshua Jordan, Jinhee Choi, Nayan Parmar, Tashrif Sanil, Tim Frenzel, Robleh Farah, Angela Franco, Ken Litton, Thomas Reeder, Andrei Cabrera, Dasha Kondratenko, Derek Sirola, Xiao Yu Omeara, Nick Kruckenberg, Mike Lauri, Rob Nobile, and Justin Jaeger, Alicia Brooks, Aditi Srivastava, Jeremy Dalton.
Installation
Installing and Connecting a Redis Server
If not already installed on your server, install Redis.
- Mac-Homebrew:
- At the terminal, type
brew install redis
- After installation completes, type
redis-server
- Your server should now have a Redis database connection open (note the port on which it is listening)
- At the terminal, type
- Linux or non-Homebrew:
- Download appropriate version of Redis from redis.io/download
- Follow installation instructions
- Once Redis is successfully installed, follow instructions to open a Redis database connection (note the port on which it is listening)
Install @quell/server
Install the NPM package from your terminal: npm i @quell/server
.
@quell/server
will be added as a dependency to your package.json file.
Quick Setup with CLI
The fastest way to get started with Quell is using our CLI tool, which automatically sets up your project with all necessary files and dependencies.
Installation
Run the Quell CLI in your project directory:
npx quell init
CLI Options
Basic Commands
# Basic initialization
npx quell init
# Initialize with example files
npx quell init --example
# Overwrite existing files
npx quell init --force
# Skip automatic dependency installation
npx quell init --skip-install
# Use JavaScript templates instead of TypeScript
npx quell init --javascript
What the CLI Creates
The CLI automatically generates these files in your project:
Configuration Files
.env
- Environment variables for Redis and caching configurationquell-config.ts
- Main Quell configuration with schema integration.gitignore
- Updated with Quell-specific entries
Example Files (with --example
flag)
src/server/example-server.ts
- Complete Express server with Quell middlewaresrc/server/schema/example-schema.ts
- Sample GraphQL schema with resolvers
Package Configuration
package.json
- Updated with necessary scripts and dependencies
Generated Dependencies
The CLI automatically installs these packages:
Production Dependencies:
express
- Web frameworkgraphql
- GraphQL implementationredis
- Redis clientdotenv
- Environment variable loader
Development Dependencies:
nodemon
- Development server with auto-reloadtypescript
- TypeScript compilerts-node
- TypeScript execution engine@types/express
- Express type definitions@types/node
- Node.js type definitions
Post-Installation Setup
After running the CLI, follow these steps:
1. Configure Redis
Update your .env
file with your Redis connection details:
# Local Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# Or Redis Cloud/hosted service
REDIS_HOST=your-redis-host.com
REDIS_PORT=12345
REDIS_PASSWORD=your-password
2. Update Your Schema
Replace the example schema in quell-config.ts
with your actual GraphQL schema:
import { QuellCache } from '@quell/server';
import { yourSchema } from './path/to/your/schema'; // Replace this
export const quellCache = new QuellCache({
schema: yourSchema, // Use your actual schema
cacheExpiration: Number(process.env.CACHE_EXPIRATION || 1209600),
redisPort: Number(process.env.REDIS_PORT || 6379),
redisHost: process.env.REDIS_HOST || "127.0.0.1",
redisPassword: process.env.REDIS_PASSWORD || "",
});
3. Start Development
# Start the development server
npm run dev
# Visit your GraphQL endpoint
# http://localhost:4000/graphql
Implementation Guide
QuellCache vs QuellRouter
Quell provides two complementary caching strategies:
QuellCache - For Local Schemas
- Use Case: Your own GraphQL server with direct schema access
- Caching: Normalized, entity-based caching
- Features:
- Field-level cache invalidation
- Automatic cache key generation
- Mutation-aware cache updates
- Schema introspection for universal compatibility
import { QuellCache } from '@quell/server';
const quellCache = new QuellCache({
schema: yourGraphQLSchema,
cacheExpiration: 3600,
redisHost: process.env.REDIS_HOST,
redisPort: Number(process.env.REDIS_PORT)
});
app.use('/graphql',
quellCache.rateLimiter,
quellCache.costLimit,
quellCache.depthLimit,
quellCache.query,
(req, res) => res.json({ queryResponse: res.locals })
);
QuellRouter - For 3rd Party APIs
- Use Case: External GraphQL APIs (GitHub, Contentful, etc.)
- Caching: Query-based key-value caching
- Features:
- Hash-based cache keys
- API-specific cache namespacing
- Custom headers per API
- Automatic cache TTL management
import { createQuellRouter } from '@quell/server';
const quellRouter = createQuellRouter({
endpoints: {
'/graphql': 'local', // Use QuellCache
'/graphql/github': 'https://api.github.com/graphql', // External API
'/graphql/spacex': 'https://api.spacex.land/graphql' // External API
},
cache: quellCache.redisCache,
cacheExpiration: 3600,
debug: true,
headers: {
github: {
'Authorization': 'Bearer your-github-token',
'User-Agent': 'YourApp/1.0'
}
}
});
app.use(quellRouter);
Hybrid Implementation
Combine both for maximum flexibility:
import express from 'express';
import { QuellCache, createQuellRouter } from '@quell/server';
import { localSchema } from './schema';
const app = express();
// Initialize QuellCache for local schema
const quellCache = new QuellCache({
schema: localSchema,
cacheExpiration: 3600
});
// Create router for handling both local and external APIs
const quellRouter = createQuellRouter({
endpoints: {
'/graphql': 'local', // Routes to QuellCache
'/graphql/external': 'https://api.external.com/graphql'
},
cache: quellCache.redisCache,
debug: process.env.NODE_ENV === 'development'
});
// Apply router first (handles routing logic)
app.use(quellRouter);
// Local GraphQL endpoint (processed after router)
app.use('/graphql',
quellCache.query,
(req, res) => res.json({ queryResponse: res.locals })
);
// Cache management endpoints
app.get('/clear-cache', quellCache.clearCache);
app.get('/clear-external-cache', async (req, res) => {
const cleared = await quellRouter.clearApiCache('external');
res.json({ message: `Cleared ${cleared} external cache entries` });
});
That's it! You now have a normalized cache for your GraphQL endpoint.
Rate and Cost Limiting Implementation
@quell/server now offers optional cost- and rate-limiting of incoming GraphQL queries for additional endpoint security from malicious nested or costly queries.
Both of these middleware packages use an optional "Cost Object" parameter in the QuellCache constructor. Below is an example of the default Cost Object.
const defaultCostParams = {
maxCost: 5000, // maximum cost allowed before a request is rejected
mutationCost: 5, // cost of a mutation
objectCost: 2, // cost of retrieving an object
scalarCost: 1, // cost of retrieving a scalar
depthCostFactor: 1.5, // multiplicative cost of each depth level
depthMax: 10, // maximum depth allowed before a request is rejected
ipRate: 3 // maximum subsequent calls per second before a request is rejected
}
When parsing an incoming query, @quell/server will build a cost associated with the query relative to how laborious it is to retrieve by using the costs provided in the Cost Object. The costs listed above are the default costs given upon QuellCache instantiation, but these costs can be manually reassigned upon cache creation.
If the cost of a query ever exceeds the maxCost
defined in our Cost Object, the query will be rejected and return Status 400 before the request is sent to the database. Additionally, if the depth of a query ever exceeds the depthMax
defined in our Cost Object, the query will be similarly rejected.
The ipRate
variable limits the ammount of requests a user can submit per second. Any requests above this threshold will be invalidated.
Using the implementation described in our "Cache Implementation" section, we could implement depth- and cost-limiting like so:
// instantiate quell-server
const quellCache = new QuellCache({
schema: myGraphQLSchema,
cacheExpiration: 3600,
redisPort: REDIS_PORT,
redisHost: REDIS_HOST,
redisPassword: PASSWORD,
costParameters: { maxCost: 100, depthMax: 5, ipRate: 5 }
});
// GraphQL route and Quell middleware
app.use('/graphql',
quellCache.rateLimit, // optional middleware to include ip rate limiting
quellCache.costLimit, // optional middleware to include cost limiting
quellCache.depthLimit,// optional middleware to include depth limiting
quellCache.query,
(req, res) => {
return res
.status(200)
.send(res.locals);
}
);
Note: Both of these middleware packages work individually or combined, with or without the caching provided by quellCache.query
.
Schema Compatibility Layer
Quell uses GraphQL introspection to achieve universal schema compatibility:
// Any schema format works
const apolloSchema = makeExecutableSchema({ typeDefs, resolvers });
const vanillaSchema = buildSchema(`type Query { hello: String }`);
const customSchema = new GraphQLSchema({ query: queryType });
// All automatically converted to standardized format
const quellCache = new QuellCache({ schema: anySchema });
Usage Notes
Caching Behavior
@quell/server reads GraphQL queries from
request.body.query
and attaches responses toresponse.locals.queryResponse
Cacheable queries must include ID fields (
id
,_id
,Id
, orID
) for unique entity identification
Supported operations:
- Queries with variables, arguments, nested fields, fragments, and aliases
- Mutations (add/update/delete) with automatic cache invalidation
Non-cacheable operations (executed without caching):
- Queries with GraphQL directives (
@include
,@skip
) - Subscription operations
- Queries with introspection fields (starting with
__
) Queries missing ID fields
Universal schema compatibility works with any GraphQL schema format through introspection
Contributors
@quell/server is an open-source NPM package accelerated by OS Labs and developed by Cassidy Komp, Andrew Dai, Stacey Lee, Ian Weinholtz, Angelo Chengcuenca, Emily Hoang, Keely Timms, Yusuf Bhaiyat, Chang Cai, Robert Howton, Joshua Jordan, Jinhee Choi, Nayan Parmar, Tashrif Sanil, Tim Frenzel, Robleh Farah, Angela Franco, Ken Litton, Thomas Reeder, Andrei Cabrera, Dasha Kondratenko, Derek Sirola, Xiao Yu Omeara, Nick Kruckenberg, Mike Lauri, Rob Nobile, and Justin Jaeger, Alicia Brooks, Aditi Srivastava, Jeremy Dalton.