This directory contains an example implementation for an API gateway on top of a Graphcool CRUD API. The idea is to customize the API operations that are exposed to your clients by hiding the original CRUD API and defining a custom schema on top of it.
The API gateway uses dedicated tooling that allows to easily implement a mapping from the custom schema to the underlying CRUD API.
Try out the read-only demo.
curl https://codeload.github.com/graphcool/framework/tar.gz/master | tar -xz --strip=2 framework-master/examples/typescript-gateway-custom-schema
cd typescript-gateway-custom-schema
If you haven't already, go ahead and install the Graphcool CLI:
npm install -g graphcool
The next step is to deploy the Graphcool service that's defined inside the service
directory:
cd service
graphcool deploy
When prompted which cluster you'd like to deploy, choose any of the Shared Clusters (shared-eu-west-1
, shared-ap-northeast-1
or shared-us-west-2
) rather than local
.
Then copy the endpoint for the Simple API
, you'll need it in the next step.
The service you just deployed provides a CRUD API for the User
and Post
model types that are defined in ./service/types.graphql
.
The goal of the gateway server is now to create a custom GraphQL API that only exposes variants of the underlying CRUD API.
You first need to connect the gateway to the CRUD API.
Paste the the HTTP endpoint for the Simple API
from the previous step into ./gateway/index.ts
as the value for endpoint
, replacing the current placeholder __SIMPLE_API_ENDPOINT__
:
const endpoint = '__SIMPLE_API_ENDPOINT__' // looks like: https://api.graph.cool/simple/v1/__SERVICE_ID__
Note: If you ever lose your API endpoint, you can get access to it again by running
graphcool info
in the root directory of your service (wheregraphcool.yml
is located).
Navigate into the gateway
directory, install the node dependencies and start the server:
cd ../gateway
yarn install
yarn start
The API that's exposed by the gateway is now available inside a GraphQL Playground under the following URL:
http://localhost:3000/playground
Before you start running queries against the API gateway, you should create somme dummy data in your service's database. You'll do this with a GraphQL Playground that's running against the CRUD API (not the API gateway).
Navigate back into the service
directory and open a Playground:
cd ../server
graphcool playground
In the Playground, send the following mutation to create a new User
node along with three Post
nodes:
mutation {
createUser(
name: "John",
alias: "john",
posts: [
{ title: "GraphQL is awesome" },
{ title: "Declarative data fetching with GraphQL" },
{ title: "GraphQL & Serverless" }
]
) {
id
}
}
Note: It's important the
alias
of theUser
is set tojohn
. Otherwise the API gateway won't return any data since the alias in this example is hardcoded.
Now, that there's some initial data in the database, you can use the API gateway to fetch the data through the exposed API. Note that these queries have to be run in the Playground that's running on your localhost: http://localhost:3000/playground
.
Send the following query to fetch the posts that you just created:
{
viewer {
me {
id
name
}
topPosts (limit: 2) {
title
}
}
}
The API gateway is a thin layer on top of the Graphcool service's CRUD API. For this example, the CRUD API is based on the following data model defined in the service's types.graphql
:
type User @model {
id: ID! @isUnique
name: String!
alias: String! @isUnique
posts: [Post!]! @relation(name: "UserPosts")
}
type Post @model {
id: ID! @isUnique
title: String!
author: User! @relation(name: "UserPosts")
}
Everyone who has access to the HTTP endpoint of the CRUD API can see that it exposes the following operations:
type Query {
# Read operations for `Post`
Post(id: ID): Post
allPosts(filter: PostFilter, orderBy: PostOrderBy, skip: Int, after: String, before: String, first: Int, last: Int): [Post!]!
# Read operations for `User`
User(alias: String, id: ID): User
allUsers(filter: UserFilter, orderBy: UserOrderBy, skip: Int, after: String, before: String, first: Int, last: Int): [User!]!
}
type Mutation {
# Create, update, delete operations for `Post`
createPost(title: String!, authorId: ID, author: PostauthorUser): Post
updatePost(id: ID!, title: String, authorId: ID, author: PostauthorUser): Post
deletePost(id: ID!): Post
# Create, update, delete operations for `User`
createUser(alias: String!, name: String!, postsIds: [ID!], posts: [UserpostsPost!]): User
updateUser(alias: String, id: ID!, name: String, postsIds: [ID!], posts: [UserpostsPost!]): User
deleteUser(id: ID!): User
# Set relation between `Post` and `User` node
addToUserPosts(postsPostId: ID!, authorUserId: ID!): AddToUserPostsPayload
}
The API gateway now creates another API that will be exposed to the clients. The server that exposes this API is executing its queries against the underlying CRUD API. The magic enabling this functionality is implemented in the run
function in index.ts
.
Here's the schema that defines the new API:
type Query {
viewer: Viewer!
}
type Viewer {
me: User
topPosts(limit: Int): [Post!]!
}
There are four major steps that are being performed to map the CRUD API to the new schema:
- Create local version of the CRUD API using
makeRemoteExecutableSchema
. See the code. - Define schema for the new API (the one exposed by the API gateway). See the code.
- Merge remote schema with new schema using
mergeSchemas
. See the code. - Limit exposed operations from merged schemas (hiding all root fields except
viewer
) usingtransformSchema
. See the code.