mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
add rest-wrapper remote schema boilerplate (#1826)
This commit is contained in:
parent
2ee9820c22
commit
ed9a5b962a
4
community/boilerplates/remote-schemas/rest-wrapper/.gitignore
vendored
Normal file
4
community/boilerplates/remote-schemas/rest-wrapper/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
*.zip
|
||||
package-lock.json
|
||||
my-rest-api/.git
|
101
community/boilerplates/remote-schemas/rest-wrapper/README.md
Normal file
101
community/boilerplates/remote-schemas/rest-wrapper/README.md
Normal 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.
|
@ -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;
|
||||
|
52
community/boilerplates/remote-schemas/rest-wrapper/index.js
Normal file
52
community/boilerplates/remote-schemas/rest-wrapper/index.js
Normal 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}`);
|
||||
});
|
||||
|
2
community/boilerplates/remote-schemas/rest-wrapper/my-rest-api/.gitignore
vendored
Normal file
2
community/boilerplates/remote-schemas/rest-wrapper/my-rest-api/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
package-lock.json
|
@ -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}!`));
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user