Skip to content

Commit

Permalink
Remove RESTful API endpoint in favor of GraphQL
Browse files Browse the repository at this point in the history
  • Loading branch information
koistya committed Feb 27, 2016
1 parent 0b745f2 commit eb0da47
Show file tree
Hide file tree
Showing 9 changed files with 60 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/data-fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
};
Expand Down
5 changes: 5 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions docs/recipes/how-to-implement-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}
Expand Down Expand Up @@ -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>;
});
Expand Down
40 changes: 18 additions & 22 deletions src/api/content.js → src/data/queries/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@

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';

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');

Expand All @@ -29,32 +35,22 @@ 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;
}

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))) {
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);
return null;
}
} catch (err) {
next(err);
}
});

export default router;
const source = await readFile(fileName, { encoding: 'utf8' });
return parseJade(path, source);
},
};
1 change: 1 addition & 0 deletions src/data/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const schema = new Schema({
name: 'Query',
fields: {
me: require('./queries/me').default,
content: require('./queries/content').default,
},
}),
});
Expand Down
26 changes: 26 additions & 0 deletions src/data/types/ContentType.js
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 3 additions & 3 deletions src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?
Expand Down
1 change: 0 additions & 1 deletion src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit eb0da47

Please sign in to comment.