- Recipes list
- Display a single recipe page
- Filter recipes
- Star a recipe
- Pagination
- path /
- path recipes/:id
- path recipes/page/:page
- List all recipes
GET /api/recipes HTTP/1.1
Host: localhost:3000
Cache-Control: no-cache
{
"data": [
{
"id": 0,
"content": {
"name": "Lemon Chicken",
"imageUrl": "http://ichef.bbci.co.uk/food/ic/food_16x9_608/recipes/lemonchicken_11654_16x9.jpg",
"ingredients": [
"Chicken",
"Lemon",
"Thyme"
],
"qty": {
"Chicken": 4,
"Lemon": 1,
"Thyme": "1 tsp"
},
"cookingTime": "30 minutes"
}
},
{
"id": 1,
"content": {
"name": "Beef Stroganoff",
"imageUrl": "http://ichef.bbci.co.uk/food/ic/food_16x9_608/recipes/beefstroganoffwithsa_89439_16x9.jpg",
"ingredients": [
"Beef",
"Mustard",
"Mushrooms"
],
"cookingTime": "30 minutes"
}
},
{
"id": 2,
"content": {
"name": "Chicken Caesar Salad",
"imageUrl": "http://ichef.bbci.co.uk/food/ic/food_16x9_608/recipes/chickencaesarsalad_84099_16x9.jpg",
"ingredients": [
"Lettuce",
"Chicken",
"Parmesan",
"Croutons"
],
"cookingTime": "25 minutes"
}
}
],
"totalItems": 3
}
- List Recipes By Page
GET /api/recipes?page=1 HTTP/1.1
Host: localhost:3000
Cache-Control: no-cache
{
"data": {
"1": [
{
"id": 0,
"content": {
"name": "Lemon Chicken",
"imageUrl": "http://ichef.bbci.co.uk/food/ic/food_16x9_608/recipes/lemonchicken_11654_16x9.jpg",
"ingredients": [
"Chicken",
"Lemon",
"Thyme"
],
"qty": {
"Chicken": 4,
"Lemon": 1,
"Thyme": "1 tsp"
},
"cookingTime": "30 minutes"
}
},
{
"id": 1,
"content": {
"name": "Beef Stroganoff",
"imageUrl": "http://ichef.bbci.co.uk/food/ic/food_16x9_608/recipes/beefstroganoffwithsa_89439_16x9.jpg",
"ingredients": [
"Beef",
"Mustard",
"Mushrooms"
],
"cookingTime": "30 minutes"
}
},
{
"id": 2,
"content": {
"name": "Chicken Caesar Salad",
"imageUrl": "http://ichef.bbci.co.uk/food/ic/food_16x9_608/recipes/chickencaesarsalad_84099_16x9.jpg",
"ingredients": [
"Lettuce",
"Chicken",
"Parmesan",
"Croutons"
],
"cookingTime": "25 minutes"
}
}
]
},
"totalPage": 1,
"page": "1",
"itemsPerPage": 3,
"totalItems": 3
}
A boilerplate doing universal/isomorphic rendering with Redux + React-router + Express, based on Redux-Realword-Example
To bootstrap a React app development environment is not an easy task, there are so many libraries to setup, including webpack, babel, testing stuff and others. I'd like this boilerplate to be a ready-to-use one with the essential tools and the simplest logic that just work to build a universal rendering React + Redux app. That's why there is no fancy stuff in this app, since it's a basis of your killer app rather than a showcase one.
-
clone this app and name it as whatever your want:
$ git clone https://github.com/mz026/universal-redux-template.git my-killer-app
-
remove the
.git
folder since you won't need the history of this boilerplate:$ cd my-killer-app; rm -rf .git
-
start out a new git history:
$ git init
-
Install dependencies:
$ yarn install
-
Host dev environment and start to build something changing the world!
$ yarn start
-
To run the test with Mocha, Enzyme, Sinon and Chai:
$ yarn test:ci
-
To generate a container/component/action and its tests:
$ ./bin/generate <type> <path>
eg: $ ./bin/generate component myNamespace/MyComponent
- Universal rendering, with async data support
- Server side redirect
- Separate vendor and app js files
- Hot Reload on client side by Webpack
- Hot Reload on server side (ref)
- react@15.4.2
- react-router@3.0.5
- Webpack@2.2
- Babel@6
- Express as isomorphic server
yarn
as package manager
- Mocha as testing framework
- Chai as assertion library
- Rewire and Sinon to mock/stub
- Enzyme to do React rendering
When developing a feature,
- First run a separate process converting ES6 to ES5 lively:
$ yarn run test:watch
- Run the test case of a single file/directory by:
$ yarn test -- <the-file-path>
For example:
$ yarn test -- app/test/actions
- Before deployment, run all the test cases to make sure everything is fine by:
$ yarn test:ci
The way suggested by babel's doc compiles the code on the fly, which is too slow, especially when the number of files grows:
//package.json
{
"scripts": {
"test": "mocha --compilers js:babel-register"
}
}
So here we pre-compile the code, watch it, and maintain a cache to avoid repeated build of the files that doesn't change. After the pre-compile, the testing cycle can be much faster than before, especially when doing TDD.
For vim users, there's a plugin called vim-test binding tests with the editor. You can trigger a test "nearest the cursor", which is super handy when doing TDD.
To make vim-test
work together with the pre-compile process,
- copy the
editor_configs/vimrc
to<project-root>/.vimrc
- make sure these two lines exist in your
~/.vimrc
to enable directory-based.vimrc
:
set exrc
set secure
Then you can enjoy the fast feedback loop powered by the greatest editor on the planet.
- Intro page:
{base_url}
- Questions Page:
{base_url}/questions
- Question Detail Page:
{base_url}/questions/:id
When handling static assets, we want to achieve the following goals:
- Make assigning assets url a no-brainer
- Apply revision when deploying to production to integrate with CDN services easily
The usage example can be found in [Intro Container](https://github.com/mz026/universal-redux-template/blob/master/app/containers/Intro.js)
We achieve this by:
First, creating an assets container folder: app/assets
Assign static folder linking /assets
to the folder above
Use a gulp task (gulp build
) to handle it:
- A set of
[rev](https://github.com/smysnk/gulp-rev-all)-ed
assets with hash code appended would be built intodist/public/assets
- A static middleware mapping root url to the folder mentioned above is configured in
server.js
- A set of
[revReplace](https://github.com/jamesknelson/gulp-rev-replace)-ed
server code would be built intodist/server-build
, so that the rev-ed assets can be used when doing server rendering
Under some cases, we'd like to do 302 redirect after fetching API. For example:
When users try to access a question page via an unexisting Id, redirect her to Index route.
In the code layer, we want the implementation to be shared on both client and server side.
This is achieved by passing a history
instance to action creators, and use history.push
whenever needed.
On the client side, react-router
would then take care the rest of redirecting logic,
while on server side, we subscribe the url-chaning events on each request, and redirect requests to proper pages if needed.
Such implementation can be found in QuestionContainer
,
questions action
Vendor related scripts are bundled into a vendor.js
,
associated settings can be found in the entry
field of webpack.config.js
.
(thanks @dlombardi for pointing it out!)
The single quotes in yarn test
script surrounding path do not work on Windows, while they're necessary on unix-like machines.
Please remove them in scripts.test
section in package.json
like this:
"test": "NODE_ENV=test NODE_PATH=./app mocha --compilers js:babel-register -r app/spec/support/setup.mocha.js --recursive app/spec/**/*.test.js -w"
(thanks @jbuffin for pointing it out!)
To deploy this app to production environment:
-
Prepare a server with NodeJS environment
-
Use whatever tool to upload this app to server. (Capistrano is a good choice.)
-
Run
$ NODE_ENV=production yarn install
on server- After the installation above,
postinstall
script will run automatically, building front-end related assets and rev-ed server code underdist/
folder.
- After the installation above,
-
Kick off the server with:
NODE_ENV=production NODE_PATH=./dist/server-build node dist/server-build/server
To deploy this app to heroku,
- Set up heroku git remote url
- Set
API_BASE_URL
to heroku config var. (without trailing slash)
Here's a sample deployed to heroku: https://redux-template-test.herokuapp.com/
For this case, the API_BASE_URL
mention above would be https://redux-template-test.herokuapp.com