commit eb0da472d2b4bbc8b69dc2e1be41edc2384c738c
parent 0b745f24a10ba849623c752e4406aec0b5d10eb3
Author: Konstantin Tarkus <hello@tarkus.me>
Date: Sat, 27 Feb 2016 16:39:03 +0300
Remove RESTful API endpoint in favor of GraphQL
Diffstat:
10 files changed, 98 insertions(+), 71 deletions(-)
diff --git a/README.md b/README.md
@@ -35,11 +35,11 @@ visit our sponsors:
├── /node_modules/ # 3rd-party libraries and utilities
├── /src/ # The source code of the application
│ ├── /actions/ # Action creators that allow to trigger a dispatch to stores
-│ ├── /api/ # REST API / Relay endpoints
│ ├── /components/ # React components
│ ├── /constants/ # Constants (action types etc.)
│ ├── /content/ # Static content (plain HTML or Markdown, Jade, you name it)
│ ├── /core/ # Core framework and utility functions
+│ ├── /data/ # GraphQL server schema
│ ├── /decorators/ # Higher-order React components
│ ├── /public/ # Static files which are copied into the /build/public folder
│ ├── /stores/ # Stores contain the application state and logic
diff --git a/docs/data-fetching.md b/docs/data-fetching.md
@@ -8,7 +8,7 @@ import fetch from '../core/fetch';
export const path = '/products';
export const action = async () => {
- const response = await fetch('/api/products');
+ const response = await fetch('/graphql?query={products{id,name}}');
const data = await response.json();
return <Layout><Products {...data} /></Layout>;
};
diff --git a/docs/getting-started.md b/docs/getting-started.md
@@ -37,6 +37,11 @@ This command will build the app from the source files (`/src`) into the output
Node.js server (`node build/server.js`) and [Browsersync](https://browsersync.io/)
with [HMR](https://webpack.github.io/docs/hot-module-replacement) on top of it.
+> [http://localhost:3000/](http://localhost:3000/) — Node.js server (`build/sever.js`)<br>
+> [http://localhost:3000/graphql](http://localhost:3000/graphql) — GraphQL server and IDE<br>
+> [http://localhost:3001/](http://localhost:3001/) — BrowserSync proxy with HMR, React Hot Transform<br>
+> [http://localhost:3002/](http://localhost:3002/) — BrowserSync control panel (UI)
+
Now you can open your web app in a browser, on mobile devices and start
hacking. Whenever you modify any of the source files inside the `/src` folder,
the module bundler ([Webpack](http://webpack.github.io/)) will recompile the
diff --git a/docs/recipes/how-to-implement-routing.md b/docs/recipes/how-to-implement-routing.md
@@ -60,12 +60,12 @@ import ErrorPage from './components/ErrorPage';
const routes = {
'/': async () => {
- const response = await fetch('/api/data/home');
+ const response = await fetch('/graphql?query={content(path:"/"){title,html}}');
const data = await response.json();
return <Layout><HomePage {...data} /></Layout>
},
'/about': async () => {
- const response = await fetch('/api/data/about');
+ const response = await fetch('/graphql?query={content(path:"/about"){title,html}}');
const data = await response.json();
return <Layout><AboutPage {...data} /></Layout>;
}
@@ -108,12 +108,12 @@ import ErrorPage from './components/ErrorPage';
const router = new Router(on => {
on('/products', async () => {
- const response = await fetch('/api/products');
+ const response = await fetch('/graphql?query={products{id,name}}');
const data = await response.json();
return <Layout><ProductListing {...data} /></Layout>
});
- on('/products/:id', async (req) => {
- const response = await fetch(`/api/products/${req.params.id}`);
+ on('/products/:id', async ({ params }) => {
+ const response = await fetch('/graphql?query={product(id:"${params.id}"){name,summary}}');
const data = await response.json();
return <Layout><ProductInfo {...data} /></Layout>;
});
diff --git a/src/api/content.js b/src/api/content.js
@@ -1,60 +0,0 @@
-/**
- * React Starter Kit (https://www.reactstarterkit.com/)
- *
- * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE.txt file in the root directory of this source tree.
- */
-
-import fs from 'fs';
-import { join } from 'path';
-import { Router } from 'express';
-import Promise from 'bluebird';
-import jade from 'jade';
-import fm from 'front-matter';
-
-// A folder with Jade/Markdown/HTML content pages
-const CONTENT_DIR = join(__dirname, './content');
-
-// Extract 'front matter' metadata and generate HTML
-const parseJade = (path, jadeContent) => {
- const fmContent = fm(jadeContent);
- const htmlContent = jade.render(fmContent.body);
- return Object.assign({ path, content: htmlContent }, fmContent.attributes);
-};
-
-const readFile = Promise.promisify(fs.readFile);
-const fileExists = filename => new Promise(resolve => {
- fs.exists(filename, resolve);
-});
-
-const router = new Router();
-
-router.get('/', async (req, res, next) => {
- try {
- const path = req.query.path;
-
- if (!path || path === 'undefined') {
- res.status(400).send({ error: 'The \'path\' query parameter cannot be empty.' });
- return;
- }
-
- let fileName = join(CONTENT_DIR, `${path === '/' ? '/index' : path}.jade`);
- if (!(await fileExists(fileName))) {
- fileName = join(CONTENT_DIR, `${path}/index.jade`);
- }
-
- if (!(await fileExists(fileName))) {
- res.status(404).send({ error: `The page '${path}' is not found.` });
- } else {
- const source = await readFile(fileName, { encoding: 'utf8' });
- const content = parseJade(path, source);
- res.status(200).send(content);
- }
- } catch (err) {
- next(err);
- }
-});
-
-export default router;
diff --git a/src/data/queries/content.js b/src/data/queries/content.js
@@ -0,0 +1,56 @@
+/**
+ * React Starter Kit (https://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import fs from 'fs';
+import { join } from 'path';
+import Promise from 'bluebird';
+import jade from 'jade';
+import fm from 'front-matter';
+
+import {
+ GraphQLString as StringType,
+ GraphQLNonNull as NonNull,
+} from 'graphql';
+
+import ContentType from '../types/ContentType';
+
+// A folder with Jade/Markdown/HTML content pages
+const CONTENT_DIR = join(__dirname, './content');
+
+// Extract 'front matter' metadata and generate HTML
+const parseJade = (path, jadeContent) => {
+ const fmContent = fm(jadeContent);
+ const htmlContent = jade.render(fmContent.body);
+ return Object.assign({ path, content: htmlContent }, fmContent.attributes);
+};
+
+const readFile = Promise.promisify(fs.readFile);
+const fileExists = filename => new Promise(resolve => {
+ fs.exists(filename, resolve);
+});
+
+export default {
+ type: ContentType,
+ args: {
+ path: { type: new NonNull(StringType) },
+ },
+ async resolve({ request }, { path }) {
+ let fileName = join(CONTENT_DIR, `${path === '/' ? '/index' : path}.jade`);
+ if (!(await fileExists(fileName))) {
+ fileName = join(CONTENT_DIR, `${path}/index.jade`);
+ }
+
+ if (!(await fileExists(fileName))) {
+ return null;
+ }
+
+ const source = await readFile(fileName, { encoding: 'utf8' });
+ return parseJade(path, source);
+ },
+};
diff --git a/src/data/schema.js b/src/data/schema.js
@@ -17,6 +17,7 @@ const schema = new Schema({
name: 'Query',
fields: {
me: require('./queries/me').default,
+ content: require('./queries/content').default,
},
}),
});
diff --git a/src/data/types/ContentType.js b/src/data/types/ContentType.js
@@ -0,0 +1,26 @@
+/**
+ * React Starter Kit (https://www.reactstarterkit.com/)
+ *
+ * Copyright © 2014-2016 Kriasoft, LLC. All rights reserved.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import {
+ GraphQLObjectType as ObjectType,
+ GraphQLString as StringType,
+ GraphQLNonNull as NonNull,
+} from 'graphql';
+
+const ContentType = new ObjectType({
+ name: 'Content',
+ fields: {
+ path: { type: new NonNull(StringType) },
+ title: { type: new NonNull(StringType) },
+ content: { type: new NonNull(StringType) },
+ component: { type: new NonNull(StringType) },
+ },
+});
+
+export default ContentType;
diff --git a/src/routes.js b/src/routes.js
@@ -31,9 +31,9 @@ const router = new Router(on => {
on('/register', async () => <RegisterPage />);
on('*', async (state) => {
- const response = await fetch(`/api/content?path=${state.path}`);
- const content = await response.json();
- return response.ok && content && <ContentPage {...content} />;
+ const response = await fetch(`/graphql?query={content(path:"${state.path}"){path,title,content,component}}`);
+ const { data } = await response.json();
+ return data && data.content && <ContentPage {...data.content} />;
});
on('error', (state, error) => state.statusCode === 404 ?
diff --git a/src/server.js b/src/server.js
@@ -69,7 +69,6 @@ server.get('/login/facebook/return',
//
// Register API middleware
// -----------------------------------------------------------------------------
-server.use('/api/content', require('./api/content').default);
server.use('/graphql', expressGraphQL(req => ({
schema,
graphiql: true,