Skip to content

Commit b343289

Browse files
authored
Merge pull request #92 from oslabs-beta/em/deploy2
documentation and deployment
2 parents 06dfee5 + 48abe17 commit b343289

File tree

3 files changed

+258
-8
lines changed

3 files changed

+258
-8
lines changed

.travis.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
11
language: node_js
22
node_js:
33
- 16
4+
- 17
5+
# - 18
6+
7+
# run test for the above node versions for branches dev and main
48
branches:
59
only:
610
- dev
11+
- main
12+
# scripts to run for each test
713
script:
14+
- echo "Running tests against $(node -v) ..."
815
- 'npm run lint'
916
- 'npm run test'
17+
- 'npm run build'
18+
# specify a job to run
19+
deploy:
20+
on:
21+
branch: main
22+
tags: true
23+
skip_cleanup: false
24+
provider: npm
25+
email: $NPM_EMAIL_ADDRESS
26+
api_key: $NPM_API_KEY
27+
28+

README.md

Lines changed: 223 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,224 @@
1-
# GraphQL-Gate
1+
<div align="center">
2+
<img width="50px" src="https://user-images.githubusercontent.com/89324687/182067950-54c00964-2be4-481a-976b-773d9112a4c0.png"/>
3+
<h1>GraphQLGate</h1>
4+
<a href="https://github.com/oslabs-beta/GraphQL-Gate"><img src="https://img.shields.io/badge/license-MIT-blue"/></a> <a href="https://github.com/oslabs- beta/GraphQL-Gate/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/oslabs-beta/GraphQL-Gate"></a> <a href="https://github.com/oslabs-beta/GraphQL-Gate/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/oslabs-beta/GraphQL-Gate"></a> <img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/oslabs-beta/GraphQL-Gate">
25

3-
A GraphQL rate limiting library using query complexity analysis.
6+
<h3 align="center"> <strong>A GraphQL rate-limiting library with query complextiy analysis for Node.js and Express</strong></h3>
7+
</div>
8+
9+
&nbsp;
10+
11+
## Table of Contents
12+
13+
- [Getting Started](#getting-started)
14+
- [Configuration](#configuration)
15+
- [Notes on Lists](#lists)
16+
- [How It Works](#how-it-works)
17+
- [Response](#response)
18+
- [Error Handling](#error-handling)
19+
- [Future Development](#future-development)
20+
- [Contributions](#contributions)
21+
- [Developers](#developers)
22+
- [License](#license)
23+
24+
## <a name="getting-started"></a> Getting Started
25+
26+
Install the package
27+
28+
```
29+
npm i graphqlgate
30+
```
31+
32+
Import the package and add the rate-limiting middlleware to the Express middleware chain before the GraphQL server.
33+
34+
NOTE: a Redis server instance will need to be started in order for the limiter to cache data.
35+
36+
```javascript
37+
// import package
38+
import expressGraphQLRateLimiter from 'graphqlgate';
39+
40+
/**
41+
* Import other dependencies
42+
* */
43+
44+
//Add the middleware into your GraphQL middleware chain
45+
app.use(
46+
'gql',
47+
expressGraphQLRateLimiter(schemaObject, {
48+
rateLimiter: {
49+
type: 'TOKEN_BUCKET',
50+
refillRate: 10,
51+
capacity: 100,
52+
},
53+
}) /** add GraphQL server here */
54+
);
55+
```
56+
57+
## <a name="configuration"></a> Configuration
58+
59+
1. #### `schema: GraphQLSchema` | required
60+
61+
2. #### `config: ExpressMiddlewareConfig` | required
62+
63+
- `rateLimiter: RateLimiterOptions` | required
64+
65+
- `type: 'TOKEN_BUCKET' | 'FIXED_WINDOW' | 'SLIDING_WINDOW_LOG' | 'SLIDING_WINDOW_COUTER'`
66+
- `capacity: number`
67+
- `refillRate: number` | bucket algorithms only
68+
- `windowSize: number` | (in ms) window algorithms only
69+
70+
- `redis: RedisConfig`
71+
72+
- `options: RedisOptions` | [ioredis configuration options](https://github.com/luin/ioredis) | defaults to standard ioredis connection options (`localhost:6379`)
73+
- `keyExpiry: number` (ms) | custom expiry of keys in redis cache | defaults to 24 hours
74+
75+
- <a name="typeWeights"></a>`typeWeights: TypeWeightObject`
76+
77+
- `mutation: number` | assigned weight to mutations | defaults to 10
78+
- `query: number` | assigned weight of a query | defaults to 1
79+
- `object: number` | assigned weight of GraphQL object, interface and union types | defaults to `1`
80+
- `scalar: number` | assigned weight of GraphQL scalar and enum types | defaults to `0`
81+
82+
- `depthLimit: number` | throttle queies by the depth of the nested stucture | defaults to `Infinity` (ie. no limit)
83+
- `enforceBoundedLists: boolean` | if true, an error will be thrown if any lists types are not bound by slicing arguments [`first`, `last`, `limit`] or directives | defaults to `false`
84+
- `dark: boolean` | if true, the package will calculate complexity, depth and tokens but not throttle any queries. Use this to dark launch the package and monitor the rate limiter's impact without limiting user requests.
85+
86+
All configuration options
87+
88+
```javascript
89+
expressGraphQLRateLimiter(schemaObject, {
90+
rateLimiter: {
91+
type: 'SLIDING_WINDOW_LOG', // rate-limiter selection
92+
windowSize: 6000, // 6 seconds
93+
capacity: 100,
94+
},
95+
redis: {
96+
keyExpiry: 14400000 // 4 hours, defaults to 86400000 (24 hours)
97+
options: {
98+
host: 'localhost' // ioredis connection options
99+
port: 6379,
100+
}
101+
},
102+
typeWeights: { // weights of GraphQL types
103+
mutation: 10,
104+
query: 1,
105+
object: 1,
106+
scalar: 0,
107+
},
108+
enforceBoundedLists: false, // defaults to false
109+
dark: false, // defaults to false
110+
depthLimit: 7 // defaults to Infinity (ie. no depth limiting)
111+
});
112+
```
113+
114+
## <a name="lists"></a> Notes on Lists
115+
116+
For queries that return a list, the complexity can be determined by providing a slicing argument to the query (`first`, `last`, `limit`), or using a schema directive.
117+
118+
1. Slicing arguments: lists must be bounded by one integer slicing argument in order to calculate the complexity for the field. This package supports the slicing arguments `first`, `last` and `limit`. The complexity of the list will be the value passed as the argument to the field.
119+
120+
2. Directives: To use directives, `@listCost` must be defined in your schema with `directive @listCost(cost: Int!) on FIELD_DEFINITION`. Then, on any field which resolves to an unbounded list, add `@listCost(cost: [Int])` where `[Int]` is the complexity for this field.
121+
122+
(Note: Slicing arguments are preferred and will override the the `@listCost` directive! `@listCost` is in place as a fall back.)
123+
124+
```graphql
125+
directive @listCost(cost: Int!) on FIELD_DEFINITION
126+
type Human {
127+
id: ID!
128+
}
129+
type Query {
130+
humans: [Human] @listCost(cost: 10)
131+
}
132+
```
133+
134+
## <a name="how-it-works"></a> How It Works
135+
136+
Requests are rate-limited based on the IP address associated with the request.
137+
138+
On startup, the GraphQL (GQL) schema is parsed to build an object that maps GQL types/fields to their corresponding weights. Type weights can be provided during <a href="typeWeights">initial configuration</a>. When a request is received, this object is used to cross reference the fields queried by the user and compute the complexity of each field. The total complexity of the request is the sum of these values.
139+
140+
Complexity is determined, statically (before any resolvers are called) to estimate the upper bound of the response size - a proxy for the work done by the server to build the response. The total complexity is then used to allow/block the request based on popular rate-limiting algorithms.
141+
142+
Requests for each user are processed sequentially by the rate limiter.
143+
144+
Example (with default weights):
145+
146+
```graphql
147+
query {
148+
# 1 query
149+
hero(episode: EMPIRE) {
150+
# 1 object
151+
name # 0 scalar
152+
id # 0 scalar
153+
friends(first: 3) {
154+
# 3 objects
155+
name # 0 scalar
156+
id # 0 scalar
157+
}
158+
}
159+
reviews(episode: EMPIRE, limit: 5) {
160+
# 5 objects
161+
stars # 0 scalar
162+
commentary # 0 scalar
163+
}
164+
} # total complexity of 10
165+
```
166+
167+
## <a name="response"></a> Response
168+
169+
1. <b>Blocked Requests</b>: blocked requests recieve a response with,
170+
171+
- status of `429` for `Too Many Requests`
172+
- `Retry-After` header indicating the time to wait in seconds before the request could be approved (`Infinity` if the complexity is greater than rate-limiting capacity).
173+
- A JSON response with the remaining `tokens` available, `complexity` of the query, `depth` of the query, `success` of the query set to `false`, and the UNIX `timestamp` of the request
174+
175+
2. <b>Successful Requests</b>: successful requests are passed on to the next function in the middleware chain with the following properties saved to `res.locals`
176+
177+
```javascript
178+
{
179+
graphglGate: {
180+
success: boolean, // true when successful
181+
tokens: number, // tokens available after request
182+
compexity: number, // complexity of the query
183+
depth: number, // depth of the query
184+
timestamp: number, // UNIX timestamp
185+
}
186+
}
187+
```
188+
189+
## <a name="error-handling"></a> Error Handling
190+
191+
- Incoming queries are validated against the GraphQL schema. If the query is invalid, a response with status code `400` is returned along with an array of GraphQL Errors that were found.
192+
- To avoid disrupting server activity, errors thrown during the analysis and rate-limiting of the query are logged and the request is passed onto the next piece of middleware in the chain.
193+
194+
## <a name="future-development"></a> Future Development
195+
196+
- Ability to use this package with other caching technologies or libraries
197+
- Implement "resolve complexity analysis" for queries
198+
- Implement leaky bucket algorithm for rate-limiting
199+
- Experiment with performance improvements
200+
- caching optimization
201+
- Ensure connection pagination conventions can be accuratly acconuted for in complexity analysis
202+
- Ability to use middleware with other server frameworks
203+
204+
## <a name="contributions"></a> Contributions
205+
206+
Contributions to the code, examples, documentation, etc. are very much appreciated.
207+
208+
- Please report issues and bugs directly in this [GitHub project](https://github.com/oslabs-beta/GraphQL-Gate/issues).
209+
210+
## <a name="developers"></a> Developers
211+
212+
- [Evan McNeely](https://github.com/evanmcneely)
213+
- [Stephan Halarewicz](https://github.com/shalarewicz)
214+
- [Flora Yufei Wu](https://github.com/feiw101)
215+
- [Jon Dewey](https://github.com/donjewey)
216+
- [Milos Popovic](https://github.com/milos381)
217+
218+
## <a name="license"></a> License
219+
220+
This product is licensed under the MIT License - see the LICENSE.md file for details.
221+
222+
This is an open source product.
223+
224+
This product is accelerated by OS Labs.

package.json

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
{
2-
"name": "graphql-gate",
2+
"name": "graphqlgate",
33
"version": "1.0.0",
44
"description": "A GraphQL rate limiting library using query complexity analysis.",
55
"main": "index.js",
66
"type": "module",
7+
"files": ["src"],
78
"scripts": {
89
"test": "jest --passWithNoTests --coverage --detectOpenHandles",
910
"lint": "eslint src test",
@@ -14,15 +15,24 @@
1415
},
1516
"repository": {
1617
"type": "git",
17-
"url": "git+https://github.com/oslabs-beta/graph-beaver.git"
18+
"url": "git+https://github.com/oslabs-beta/graphql-gate.git"
1819
},
19-
"keywords": [],
20-
"author": "",
20+
"keywords": [
21+
"graphql",
22+
"graphqlgate",
23+
"rate-limiting",
24+
"throttling",
25+
"query",
26+
"express",
27+
"complexity",
28+
"analysis"
29+
],
30+
"author": "Evan McNeely, Stephan Halarewicz, Flora Yufei Wu, Jon Dewey, Milos Popovic",
2131
"license": "ISC",
2232
"bugs": {
23-
"url": "https://github.com/oslabs-beta/graph-beaver/issues"
33+
"url": "https://github.com/oslabs-beta/GraphQL-Gate/issues"
2434
},
25-
"homepage": "https://github.com/oslabs-beta/graph-beaver#readme",
35+
"homepage": "https://github.com/oslabs-beta/GraphQL-Gate#readme",
2636
"devDependencies": {
2737
"@babel/core": "^7.17.12",
2838
"@babel/preset-env": "^7.17.12",

0 commit comments

Comments
 (0)