add rest-wrapper remote schema boilerplate (#1826)

This commit is contained in:
Tirumarai Selvan 2019-03-20 16:15:45 +05:30 committed by Shahidh K Muhammed
parent 2ee9820c22
commit ed9a5b962a
8 changed files with 281 additions and 0 deletions

View File

@ -0,0 +1,4 @@
node_modules/
*.zip
package-lock.json
my-rest-api/.git

View File

@ -0,0 +1,101 @@
# REST wrapper - Boilerplate to write a GraphQL server that wraps a REST API
This boilerplate gives an example of writing a GraphQL service to wrap some pre-existing REST API.
You can add this REST wrapper as a Remote Schema in Hasura.
## Stack
Node 8.10
Apollo Server (GraphQL framework)
## REST API
The REST API is implemented in [my-rest-api](my-rest-api/) folder. It has the following APIs:
```
GET /users
GET /users/:userId
POST /users
```
The `GET /users` endpoint also takes an optional query param i.e. `GET /users?name=abc` and the `POST /users` endpoint expects a valid JSON payload in the body.
## GraphQL API
We will convert the above REST API into the following GraphQL API:
```
type User {
id: String!
name: String!
balance: Int!
}
type Query {
getUser(id: String!): User
users(name: String): [User]
}
type Mutation {
addUser(name: String!, balance: Int!): User
}
```
## How to wrap
In our GraphQL service, we have defined a new API for each REST endpoint. This is what our mapping looks like:
| REST | GraphQL |
|---------------------|------------------------------------------------|
| GET /users | users (name: String) : [User] |
| GET /users/:userId | getUser(id: String!): User |
| POST /users | addUser(name: String!, balance: Int!): User |
We would have to write a resolver for each API. This is what a typical resolver looks like, for e.g `getUser` :
```
getUser: async (_, { id }) => {
return await getData(restAPIEndpoint + '/users/' + id);
}
```
## Deployment (Using Heroku)
You need a Heroku account and heroku-cli installed. Execute the following commands in a terminal:
1. Log into Heroku
```bash
heroku login
```
2. Create REST API app
```bash
# in my-rest-api directory (community/boilerplates/remote-schemas/rest-wrapper/my-rest-api)
heroku create
```
3. Deploy REST API app
```bash
git push heroku master
```
4. The above step will return an endpoint for your REST API. Update the constant `restAPIEndpoint` in `index.js` with this endpoint.
5. Create GRAPHQL API app
```bash
# in current directory (community/boilerplates/remote-schemas/rest-wrapper)
heroku create
```
6. Deploy GRAPHQL API app
```bash
git push heroku master
```
The final step will also return a HTTPS URL in the output. Now, you can go to Hasura console and add this URL as a Remote Schema to allow querying it via Hasura.

View File

@ -0,0 +1,44 @@
const { ApolloError } = require('apollo-server');
const fetch = require('node-fetch');
const getData = async url => {
try {
const res = await fetch(url);
const json = await res.json();
if(isHTTPError(res.status)) {
throw new ApolloError(json, "http-status-error", {statusCode: res.status, error: json});
}
console.log(json);
return json;
} catch (error) {
console.log(JSON.stringify(error));
throw error;
}
};
const postData = async (url, body) => {
try {
const res = await fetch(url, {
method: 'post',
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
});
const json = await res.json();
if(isHTTPError(res.status)) {
throw new ApolloError(json, "http-status-error", {statusCode: res.status, error: json});
}
console.log(json);
return json;
} catch (error) {
console.log(JSON.stringify(error));
throw error;
}
};
const isHTTPError = status => {
return !((status >= 200) && (status < 300));
};
exports.getData = getData;
exports.postData = postData;

View File

@ -0,0 +1,52 @@
const { ApolloServer } = require('apollo-server');
const gql = require('graphql-tag');
const {getData, postData} = require('./helpers');
const typeDefs = gql`
type User {
id: String!
name: String!
balance: Int!
}
type Query {
getUser(id: String!): User
users(name: String): [User]
}
type Mutation {
addUser(name: String!, balance: Int!): User
}
`;
// replace with actual REST endpoint
const restAPIEndpoint = 'https://rest-user-api.herokuapp.com';
const resolvers = {
Query: {
getUser: async (_, { id }) => {
return await getData(restAPIEndpoint + '/users/' + id);
},
users: async (_, { name }) => {
var nameParams = '';
if (name) {
nameParams = '?name=' + name;
}
return await getData(restAPIEndpoint + '/users' + nameParams );
}
},
Mutation: {
addUser: async (_, { name, balance } ) => {
return await postData(restAPIEndpoint + '/users', { name, balance } );
}
}
};
const schema = new ApolloServer({ typeDefs, resolvers });
schema.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
console.log(`schema ready at ${url}`);
});

View File

@ -0,0 +1,2 @@
node_modules/
package-lock.json

View File

@ -0,0 +1,45 @@
const express = require('express');
const bodyParser = require("body-parser");
const uuidv4 = require('uuid/v4');
const app = express();
const port = process.env.PORT || 3000;
app.use(bodyParser.json());
var users = [];
app.get('/', (req, res) => res.json('Hello World!'));
app.get('/users/:userId', (req, res) => {
var idParam = req.params.userId;
var result = users.find(user => idParam ? user.id == idParam: false);
if(result) {
res.json(result);
} else {
res.status(404).json("user not found");
}
});
app.get('/users', (req, res) => {
var nameParam = req.query.name;
var result = users.filter(user => nameParam ? user.name == nameParam : true);
res.json(result);
});
app.post('/users', (req, res) => {
var user = req.body;
user.id = user.id ? user.id : uuidv4();
if (user.id && user.name && user.balance) {
if (user.balance >= 100) {
users.push(user);
res.json(user);
} else {
res.status(400).json('minimum balance required: 100');
}
} else {
res.status(400).json('invalid parameters');
}
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

View File

@ -0,0 +1,16 @@
{
"name": "rest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.4",
"uuid": "^3.3.2"
}
}

View File

@ -0,0 +1,17 @@
{
"name": "rest-remote-schema",
"version": "1.0.0",
"description": "This is a GraphQL backend to wrap a REST API.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"apollo-server": "^2.4.8",
"graphql": "^14.1.1",
"graphql-tag": "^2.10.1",
"node-fetch": "^2.3.0"
}
}