diff --git a/examples/counter/index.html b/examples/counter/index.html
index 696101a536..0f72d6a041 100644
--- a/examples/counter/index.html
+++ b/examples/counter/index.html
@@ -1,6 +1,6 @@
 <html>
   <head>
-    <title>Redux Counter Example</title>
+    <title>Redux counter example</title>
   </head>
   <body>
     <div id="root">
diff --git a/examples/counter/package.json b/examples/counter/package.json
index 80e397c2cf..59599191f3 100644
--- a/examples/counter/package.json
+++ b/examples/counter/package.json
@@ -1,7 +1,7 @@
 {
-  "name": "counter-redux",
+  "name": "redux-counter-example",
   "version": "0.0.0",
-  "description": "Counter example for redux",
+  "description": "Redux counter example",
   "main": "server.js",
   "scripts": {
     "start": "node server.js",
diff --git a/examples/real-world/.babelrc b/examples/real-world/.babelrc
new file mode 100644
index 0000000000..cab5d10d92
--- /dev/null
+++ b/examples/real-world/.babelrc
@@ -0,0 +1,3 @@
+{
+  "stage": 2
+}
diff --git a/examples/real-world/actions/index.js b/examples/real-world/actions/index.js
new file mode 100644
index 0000000000..9e02f21c09
--- /dev/null
+++ b/examples/real-world/actions/index.js
@@ -0,0 +1,148 @@
+import { CALL_API, Schemas } from '../middleware/api';
+
+export const USER_REQUEST = 'USER_REQUEST';
+export const USER_SUCCESS = 'USER_SUCCESS';
+export const USER_FAILURE = 'USER_FAILURE';
+/**
+ * Fetches a single user from Github API.
+ * Relies on the custom API middleware defined in ../middleware/api.js.
+ */
+function fetchUser(login) {
+  return {
+    [CALL_API]: {
+      types: [USER_REQUEST, USER_SUCCESS, USER_FAILURE],
+      endpoint: `users/${login}`,
+      schema: Schemas.USER
+    }
+  };
+}
+/**
+ * Fetches a single user from Github API unless it is cached.
+ * Relies on Redux Thunk middleware.
+ */
+export function loadUser(login, requiredFields = []) {
+  return (dispatch, getState) => {
+    const user = getState().entities.users[login];
+    if (user && requiredFields.every(key => user.hasOwnProperty(key))) {
+      return null;
+    }
+
+    return dispatch(fetchUser(login));
+  };
+}
+
+export const REPO_REQUEST = 'REPO_REQUEST';
+export const REPO_SUCCESS = 'REPO_SUCCESS';
+export const REPO_FAILURE = 'REPO_FAILURE';
+/**
+ * Fetches a single repository from Github API.
+ * Relies on the custom API middleware defined in ../middleware/api.js.
+ */
+function fetchRepo(fullName) {
+  return {
+    [CALL_API]: {
+      types: [REPO_REQUEST, REPO_SUCCESS, REPO_FAILURE],
+      endpoint: `repos/${fullName}`,
+      schema: Schemas.REPO
+    }
+  };
+}
+/**
+ * Loads a single user from Github API unless it is cached.
+ * Relies on Redux Thunk middleware.
+ */
+export function loadRepo(fullName, requiredFields = []) {
+  return (dispatch, getState) => {
+    const repo = getState().entities.repos[fullName];
+    if (repo && requiredFields.every(key => repo.hasOwnProperty(key))) {
+      return null;
+    }
+
+    return dispatch(fetchRepo(fullName));
+  };
+}
+
+export const STARRED_REQUEST = 'STARRED_REQUEST';
+export const STARRED_SUCCESS = 'STARRED_SUCCESS';
+export const STARRED_FAILURE = 'STARRED_FAILURE';
+/**
+ * Fetches a page of starred repos by a particular user.
+ * Relies on the custom API middleware defined in ../middleware/api.js.
+ */
+function fetchStarred(login, nextPageUrl) {
+  return {
+    login,
+    [CALL_API]: {
+      types: [STARRED_REQUEST, STARRED_SUCCESS, STARRED_FAILURE],
+      endpoint: nextPageUrl,
+      schema: Schemas.REPO_ARRAY
+    }
+  };
+}
+/**
+ * Loads a page of starred repos by a particular user.
+ * Bails out if page is cached and user didn’t specifically request next page.
+ * Relies on Redux Thunk middleware.
+ */
+export function loadStarred(login, nextPage) {
+  return (dispatch, getState) => {
+    const {
+      nextPageUrl = `users/${login}/starred`,
+      pageCount = 0
+    } = getState().pagination.starredByUser[login] || {};
+
+    if (pageCount > 0 && !nextPage) {
+      return null;
+    }
+
+    return dispatch(fetchStarred(login, nextPageUrl));
+  };
+}
+
+
+export const STARGAZERS_REQUEST = 'STARGAZERS_REQUEST';
+export const STARGAZERS_SUCCESS = 'STARGAZERS_SUCCESS';
+export const STARGAZERS_FAILURE = 'STARGAZERS_FAILURE';
+/**
+ * Fetches a page of stargazers for a particular repo.
+ * Relies on the custom API middleware defined in ../middleware/api.js.
+ */
+function fetchStargazers(fullName, nextPageUrl) {
+  return {
+    fullName,
+    [CALL_API]: {
+      types: [STARGAZERS_REQUEST, STARGAZERS_SUCCESS, STARGAZERS_FAILURE],
+      endpoint: nextPageUrl,
+      schema: Schemas.USER_ARRAY
+    }
+  };
+}
+/**
+ * Loads a page of stargazers for a particular repo.
+ * Bails out if page is cached and user didn’t specifically request next page.
+ * Relies on Redux Thunk middleware.
+ */
+export function loadStargazers(fullName, nextPage) {
+  return (dispatch, getState) => {
+    const {
+      nextPageUrl = `repos/${fullName}/stargazers`,
+      pageCount = 0
+    } = getState().pagination.stargazersByRepo[fullName] || {};
+
+    if (pageCount > 0 && !nextPage) {
+      return null;
+    }
+
+    return dispatch(fetchStargazers(fullName, nextPageUrl));
+  };
+}
+
+export const RESET_ERROR_MESSAGE = 'RESET_ERROR_MESSAGE';
+/**
+ * Resets the currently visible error message.
+ */
+export function resetErrorMessage() {
+  return {
+    type: RESET_ERROR_MESSAGE
+  };
+}
diff --git a/examples/real-world/components/Explore.js b/examples/real-world/components/Explore.js
new file mode 100644
index 0000000000..7787aa5d7c
--- /dev/null
+++ b/examples/real-world/components/Explore.js
@@ -0,0 +1,61 @@
+import React, { Component, PropTypes, findDOMNode } from 'react';
+
+const GITHUB_REPO = 'https://github.com/gaearon/redux';
+
+export default class Explore extends Component {
+  constructor(props) {
+    super(props);
+    this.handleKeyUp = this.handleKeyUp.bind(this);
+    this.handleGoClick = this.handleGoClick.bind(this);
+  }
+
+  getInputValue() {
+    return findDOMNode(this.refs.input).value;
+  }
+
+  setInputValue(val) {
+    // Generally mutating DOM is a bad idea in React components,
+    // but doing this for a single uncontrolled field is less fuss
+    // than making it controlled and maintaining a state for it.
+    findDOMNode(this.refs.input).value = val;
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.value !== this.props.value) {
+      this.setInputValue(nextProps.value);
+    }
+  }
+
+  render() {
+    return (
+      <div>
+        <p>Type a username or repo full name and hit 'Go':</p>
+        <input size='45'
+               ref='input'
+               defaultValue={this.props.value}
+               onKeyUp={this.handleKeyUp} />
+        <button onClick={this.handleGoClick}>
+          Go!
+        </button>
+        <p>
+          Code on <a href={GITHUB_REPO} target='_blank'>Github</a>.
+        </p>
+      </div>
+    );
+  }
+
+  handleKeyUp(e) {
+    if (e.keyCode === 13) {
+      this.handleGoClick();
+    }
+  }
+
+  handleGoClick() {
+    this.props.onChange(this.getInputValue())
+  }
+}
+
+Explore.propTypes = {
+  value: PropTypes.string.isRequired,
+  onChange: PropTypes.func.isRequired
+};
diff --git a/examples/real-world/components/List.js b/examples/real-world/components/List.js
new file mode 100644
index 0000000000..83a218870e
--- /dev/null
+++ b/examples/real-world/components/List.js
@@ -0,0 +1,50 @@
+import React, { Component, PropTypes } from 'react';
+
+export default class List extends Component {
+  render() {
+    const {
+      isFetching, nextPageUrl, pageCount,
+      items, renderItem, loadingLabel
+    } = this.props;
+
+    const isEmpty = items.length === 0;
+    if (isEmpty && isFetching) {
+      return <h2><i>{loadingLabel}</i></h2>;
+    }
+
+    const isLastPage = !nextPageUrl;
+    if (isEmpty && isLastPage) {
+      return <h1><i>Nothing here!</i></h1>;
+    }
+
+    return (
+      <div>
+        {items.map(renderItem)}
+        {pageCount > 0 && !isLastPage && this.renderLoadMore()}
+      </div>
+    );
+  }
+
+  renderLoadMore() {
+    const { isFetching, onLoadMoreClick } = this.props;
+    return (
+      <button style={{ fontSize: '150%' }}
+              onClick={onLoadMoreClick}
+              disabled={isFetching}>
+        {isFetching ? 'Loading...' : 'Load More'}
+      </button>
+    );
+  }
+}
+
+List.propTypes = {
+  loadingLabel: PropTypes.string.isRequired,
+  isFetching: PropTypes.bool.isRequired,
+  onLoadMoreClick: PropTypes.func.isRequired,
+  nextPageUrl: PropTypes.string
+};
+
+List.defaultProps = {
+  isFetching: true,
+  loadingLabel: 'Loading...'
+};
diff --git a/examples/real-world/components/Repo.js b/examples/real-world/components/Repo.js
new file mode 100644
index 0000000000..b9bb4fa8b0
--- /dev/null
+++ b/examples/real-world/components/Repo.js
@@ -0,0 +1,37 @@
+import React, { PropTypes } from 'react';
+import { Link } from 'react-router';
+
+export default class Repo {
+  static propTypes = {
+    repo: PropTypes.shape({
+      name: PropTypes.string.isRequired,
+      description: PropTypes.string
+    }).isRequired,
+    owner: PropTypes.shape({
+      login: PropTypes.string.isRequired
+    }).isRequired
+  }
+
+  render() {
+    const { repo, owner } = this.props;
+    const { login } = owner;
+    const { name, description } = repo;
+
+    return (
+      <div className='Repo'>
+        <h3>
+          <Link to={`/${login}/${name}`}>
+            {name}
+          </Link>
+          {' by '}
+          <Link to={`/${login}`}>
+            {login}
+          </Link>
+        </h3>
+        {description &&
+          <p>{description}</p>
+        }
+      </div>
+    );
+  }
+}
diff --git a/examples/real-world/components/User.js b/examples/real-world/components/User.js
new file mode 100644
index 0000000000..995926738a
--- /dev/null
+++ b/examples/real-world/components/User.js
@@ -0,0 +1,27 @@
+import React, { Component, PropTypes } from 'react';
+import { Link } from 'react-router';
+
+export default class User extends Component {
+  render() {
+    const { login, avatarUrl, name } = this.props.user;
+
+    return (
+      <div className='User'>
+        <Link to={`/${login}`}>
+          <img src={avatarUrl} width='72' height='72' />
+          <h3>
+            {login} {name && <span>({name})</span>}
+          </h3>
+        </Link>
+      </div>
+    );
+  }
+}
+
+User.propTypes = {
+  user: PropTypes.shape({
+    login: PropTypes.string.isRequired,
+    avatarUrl: PropTypes.string.isRequired,
+    name: PropTypes.string
+  }).isRequired
+};
diff --git a/examples/real-world/containers/App.js b/examples/real-world/containers/App.js
new file mode 100644
index 0000000000..654c978f8d
--- /dev/null
+++ b/examples/real-world/containers/App.js
@@ -0,0 +1,84 @@
+import React, { Component, PropTypes } from 'react';
+import { connect } from 'react-redux';
+import Explore from '../components/Explore';
+import { resetErrorMessage } from '../actions';
+
+class App extends Component {
+  constructor(props) {
+    super(props);
+    this.handleChange = this.handleChange.bind(this);
+    this.handleDismissClick = this.handleDismissClick.bind(this);
+  }
+
+  render() {
+    // Injected by React Router
+    const { location, children } = this.props;
+    const { pathname } = location;
+    const value = pathname.substring(1);
+
+    return (
+      <div>
+        <Explore value={value}
+                 onChange={this.handleChange} />
+        <hr />
+        {this.renderErrorMessage()}
+        {children}
+      </div>
+    );
+  }
+
+  renderErrorMessage() {
+    const { errorMessage } = this.props;
+    if (!errorMessage) {
+      return null;
+    }
+
+    return (
+      <p style={{ backgroundColor: '#e99', padding: 10 }}>
+        <b>{errorMessage}</b>
+        {' '}
+        (<a href='#'
+            onClick={this.handleDismissClick}>
+          Dismiss
+        </a>)
+      </p>
+    );
+  }
+
+  handleDismissClick(e) {
+    this.props.resetErrorMessage();
+    e.preventDefault();
+  }
+
+  handleChange(nextValue) {
+    // Available thanks to contextTypes below
+    const { router } = this.context;
+    router.transitionTo(`/${nextValue}`);
+  }
+}
+
+App.propTypes = {
+  errorMessage: PropTypes.string,
+  location: PropTypes.shape({
+    pathname: PropTypes.string.isRequired
+  }),
+  params: PropTypes.shape({
+    userLogin: PropTypes.string,
+    repoName: PropTypes.string
+  }).isRequired
+};
+
+App.contextTypes = {
+  router: PropTypes.object.isRequired
+};
+
+function mapStateToProps(state) {
+  return {
+    errorMessage: state.errorMessage
+  };
+}
+
+export default connect(
+  mapStateToProps,
+  { resetErrorMessage }
+)(App);
diff --git a/examples/real-world/containers/RepoPage.js b/examples/real-world/containers/RepoPage.js
new file mode 100644
index 0000000000..3499cffbaa
--- /dev/null
+++ b/examples/real-world/containers/RepoPage.js
@@ -0,0 +1,106 @@
+import React, { Component, PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { loadRepo, loadStargazers } from '../actions';
+import Repo from '../components/Repo';
+import User from '../components/User';
+import List from '../components/List';
+
+function loadData(props) {
+  const { fullName } = props;
+  props.loadRepo(fullName, ['description']);
+  props.loadStargazers(fullName);
+}
+
+class RepoPage extends Component {
+  constructor(props) {
+    super(props);
+    this.renderUser = this.renderUser.bind(this);
+    this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this);
+  }
+
+  componentWillMount() {
+    loadData(this.props);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.fullName !== this.props.fullName) {
+      loadData(nextProps);
+    }
+  }
+
+  render() {
+    const { repo, owner, name } = this.props;
+    if (!repo || !owner) {
+      return <h1><i>Loading {name} details...</i></h1>;
+    }
+
+    const { stargazers, stargazersPagination } = this.props;
+    return (
+      <div>
+        <Repo repo={repo}
+                    owner={owner} />
+        <hr />
+        <List renderItem={this.renderUser}
+              items={stargazers}
+              onLoadMoreClick={this.handleLoadMoreClick}
+              loadingLabel={`Loading stargazers of ${name}...`}
+              {...stargazersPagination} />
+      </div>
+    );
+  }
+
+  renderUser(user) {
+    return (
+      <User user={user}
+            key={user.login} />
+    );
+  }
+
+  handleLoadMoreClick() {
+    this.props.loadStargazers(this.props.fullName, true);
+  }
+}
+
+RepoPage.propTypes = {
+  repo: PropTypes.object,
+  fullName: PropTypes.string.isRequired,
+  name: PropTypes.string.isRequired,
+  stargazers: PropTypes.array.isRequired,
+  stargazersPagination: PropTypes.object,
+  loadRepo: PropTypes.func.isRequired,
+  loadStargazers: PropTypes.func.isRequired
+};
+
+function mapStateToProps(state) {
+  return {
+    entities: state.entities,
+    stargazersByRepo: state.pagination.stargazersByRepo
+  };
+}
+
+function mergeProps(stateProps, dispatchProps, ownProps) {
+  const { entities, stargazersByRepo } = stateProps;
+  const { login, name } = ownProps.params;
+
+  const fullName = `${login}/${name}`;
+  const repo = entities.repos[fullName];
+  const owner = entities.users[login];
+
+  const stargazersPagination = stargazersByRepo[fullName] || { ids: [] };
+  const stargazers = stargazersPagination.ids.map(id => entities.users[id]);
+
+  return Object.assign({}, dispatchProps, {
+    fullName,
+    name,
+    repo,
+    owner,
+    stargazers,
+    stargazersPagination
+  });
+}
+
+export default connect(
+  mapStateToProps,
+  { loadRepo, loadStargazers },
+  mergeProps
+)(RepoPage);
diff --git a/examples/real-world/containers/Root.js b/examples/real-world/containers/Root.js
new file mode 100644
index 0000000000..355e786cd1
--- /dev/null
+++ b/examples/real-world/containers/Root.js
@@ -0,0 +1,30 @@
+import React, { Component } from 'react';
+import { Provider } from 'react-redux';
+import { Router, Route } from 'react-router';
+import createAsyncExampleStore from '../store/createAsyncExampleStore';
+import App from './App';
+import UserPage from './UserPage';
+import RepoPage from './RepoPage';
+
+const store = createAsyncExampleStore();
+
+export default class Root extends Component {
+  render() {
+    return (
+      <div>
+        <Provider store={store}>
+          {() =>
+            <Router history={this.props.history}>
+              <Route path='/' component={App}>
+                <Route path='/:login/:name'
+                       component={RepoPage} />
+                <Route path='/:login'
+                       component={UserPage} />
+              </Route>
+            </Router>
+          }
+        </Provider>
+      </div>
+    );
+  }
+}
diff --git a/examples/real-world/containers/UserPage.js b/examples/real-world/containers/UserPage.js
new file mode 100644
index 0000000000..4b333a8012
--- /dev/null
+++ b/examples/real-world/containers/UserPage.js
@@ -0,0 +1,104 @@
+import React, { Component, PropTypes } from 'react';
+import { connect } from 'react-redux';
+import { loadUser, loadStarred } from '../actions';
+import User from '../components/User';
+import Repo from '../components/Repo';
+import List from '../components/List';
+import zip from 'lodash/array/zip';
+
+function loadData(props) {
+  const { login } = props;
+  props.loadUser(login, ['name']);
+  props.loadStarred(login);
+}
+
+class UserPage extends Component {
+  constructor(props) {
+    super(props);
+    this.renderRepo = this.renderRepo.bind(this);
+    this.handleLoadMoreClick = this.handleLoadMoreClick.bind(this);
+  }
+
+  componentWillMount() {
+    loadData(this.props);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (nextProps.login !== this.props.login) {
+      loadData(nextProps);
+    }
+  }
+
+  render() {
+    const { user, login } = this.props;
+    if (!user) {
+      return <h1><i>Loading {login}’s profile...</i></h1>;
+    }
+
+    const { starredRepos, starredRepoOwners, starredPagination } = this.props;
+    return (
+      <div>
+        <User user={user} />
+        <hr />
+        <List renderItem={this.renderRepo}
+              items={zip(starredRepos, starredRepoOwners)}
+              onLoadMoreClick={this.handleLoadMoreClick}
+              loadingLabel={`Loading ${login}’s starred...`}
+              {...starredPagination} />
+      </div>
+    );
+  }
+
+  renderRepo([repo, owner]) {
+    return (
+      <Repo repo={repo}
+            owner={owner}
+            key={repo.fullName} />
+    );
+  }
+
+  handleLoadMoreClick() {
+    this.props.loadStarred(this.props.login, true);
+  }
+}
+
+UserPage.propTypes = {
+  login: PropTypes.string.isRequired,
+  user: PropTypes.object,
+  starredPagination: PropTypes.object,
+  starredRepos: PropTypes.array.isRequired,
+  starredRepoOwners: PropTypes.array.isRequired,
+  loadUser: PropTypes.func.isRequired,
+  loadStarred: PropTypes.func.isRequired
+};
+
+function mapStateToProps(state) {
+  return {
+    entities: state.entities,
+    starredByUser: state.pagination.starredByUser
+  };
+}
+
+function mergeProps(stateProps, dispatchProps, ownProps) {
+  const { entities, starredByUser } = stateProps;
+  const { login } = ownProps.params;
+
+  const user = entities.users[login];
+  const starredPagination = starredByUser[login] || { ids: [] };
+  const starredRepos = starredPagination.ids.map(id => entities.repos[id]);
+  const starredRepoOwners = starredRepos.map(repo => entities.users[repo.owner]);
+
+  return Object.assign({}, dispatchProps, {
+    login,
+    user,
+    starredPagination,
+    starredRepos,
+    starredRepoOwners
+  });
+}
+
+export default connect(
+  mapStateToProps,
+  { loadUser, loadStarred },
+  mergeProps
+)(UserPage);
diff --git a/examples/real-world/index.html b/examples/real-world/index.html
new file mode 100644
index 0000000000..c26e4dad44
--- /dev/null
+++ b/examples/real-world/index.html
@@ -0,0 +1,10 @@
+<html>
+  <head>
+    <title>Redux real-world example</title>
+  </head>
+  <body>
+    <div id="root">
+    </div>
+  </body>
+  <script src="/static/bundle.js"></script>
+</html>
diff --git a/examples/real-world/index.js b/examples/real-world/index.js
new file mode 100644
index 0000000000..7127e34fa7
--- /dev/null
+++ b/examples/real-world/index.js
@@ -0,0 +1,9 @@
+import 'babel-core/polyfill';
+import React from 'react';
+import Root from './containers/Root';
+import BrowserHistory from 'react-router/lib/BrowserHistory';
+
+React.render(
+  <Root history={new BrowserHistory()} />,
+  document.getElementById('root')
+);
diff --git a/examples/real-world/middleware/api.js b/examples/real-world/middleware/api.js
new file mode 100644
index 0000000000..1b898d45e8
--- /dev/null
+++ b/examples/real-world/middleware/api.js
@@ -0,0 +1,142 @@
+import { Schema, arrayOf, normalize } from 'normalizr';
+import { camelizeKeys } from 'humps';
+import 'isomorphic-fetch';
+
+/**
+ * Extracts the next page URL from Github API response.
+ */
+function getNextPageUrl(response) {
+  const link = response.headers.get('link');
+  if (!link) {
+    return null;
+  }
+
+  const nextLink = link.split(',').filter(s => s.indexOf('rel="next"') > -1)[0];
+  if (!nextLink) {
+    return null;
+  }
+
+  return nextLink.split(';')[0].slice(1, -1);
+}
+
+const API_ROOT = 'https://api.github.com/';
+
+/**
+ * Fetches an API response and normalizes the result JSON according to schema.
+ * This makes every API response have the same shape, regardless of how nested it was.
+ */
+function callApi(endpoint, schema) {
+  if (endpoint.indexOf(API_ROOT) === -1) {
+    endpoint = API_ROOT + endpoint;
+  }
+
+  return fetch(endpoint)
+    .then(response =>
+      response.json().then(json => ({ json, response}))
+    ).then(({ json, response }) => {
+      if (!response.ok) {
+        return Promise.reject(json);
+      }
+
+      const camelizedJson = camelizeKeys(json);
+      const nextPageUrl = getNextPageUrl(response) || undefined;
+
+      return {
+        ...normalize(camelizedJson, schema),
+        nextPageUrl
+      };
+    });
+}
+
+// We use this Normalizr schemas to transform API responses from a nested form
+// to a flat form where repos and users are placed in `entities`, and nested
+// JSON objects are replaced with their IDs. This is very convenient for
+// consumption by reducers, because we can easily build a normalized tree
+// and keep it updated as we fetch more data.
+
+// Read more about Normalizr: https://github.com/gaearon/normalizr
+
+const userSchema = new Schema('users', {
+  idAttribute: 'login'
+});
+
+const repoSchema = new Schema('repos', {
+  idAttribute: 'fullName'
+});
+
+repoSchema.define({
+  owner: userSchema
+});
+
+/**
+ * Schemas for Github API responses.
+ */
+export const Schemas = {
+  USER: userSchema,
+  USER_ARRAY: arrayOf(userSchema),
+  REPO: repoSchema,
+  REPO_ARRAY: arrayOf(repoSchema)
+};
+
+/**
+ * Action key that carries API call info interpreted by this Redux middleware.
+ */
+export const CALL_API = Symbol('Call API');
+
+/**
+ * A Redux middleware that interprets actions with CALL_API info specified.
+ * Performs the call and promises when such actions are dispatched.
+ */
+export default store => next => action => {
+  const callAPI = action[CALL_API];
+  if (typeof callAPI === 'undefined') {
+    return next(action);
+  }
+
+  let { endpoint } = callAPI;
+  const { schema, types, bailout } = callAPI;
+
+  if (typeof endpoint === 'function') {
+    endpoint = endpoint(store.getState());
+  }
+
+  if (typeof endpoint !== 'string') {
+    throw new Error('Specify a string endpoint URL.');
+  }
+  if (!schema) {
+    throw new Error('Specify on of the exported Schemas.');
+  }
+  if (!Array.isArray(types) || types.length !== 3) {
+    throw new Error('Expected an array of three action types.');
+  }
+  if (!types.every(type => typeof type === 'string')) {
+    throw new Error('Expected action types to be strings.');
+  }
+  if (typeof bailout !== 'undefined' && typeof bailout !== 'function') {
+    throw new Error('Expected bailout to either be undefined or a function.');
+  }
+
+  if (bailout && bailout(store.getState())) {
+    return Promise.resolve();
+  }
+
+  function actionWith(data) {
+    const finalAction = Object.assign({}, action, data);
+    delete finalAction[CALL_API];
+    return finalAction;
+  }
+
+  const [requestType, successType, failureType] = types;
+  next(actionWith({ type: requestType }));
+
+  return callApi(endpoint, schema).then(
+    response => next(actionWith({
+      response,
+      type: successType
+    })),
+    error => next(actionWith({
+      type: failureType,
+      error: error.message || 'Something bad happened'
+    }))
+  );
+};
diff --git a/examples/real-world/package.json b/examples/real-world/package.json
new file mode 100644
index 0000000000..7b24a18702
--- /dev/null
+++ b/examples/real-world/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "redux-real-world-example",
+  "version": "0.0.0",
+  "description": "Redux real-world example",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/gaearon/redux.git"
+  },
+  "keywords": [
+    "react",
+    "reactjs",
+    "hot",
+    "reload",
+    "hmr",
+    "live",
+    "edit",
+    "webpack",
+    "flux"
+  ],
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/gaearon/redux/issues"
+  },
+  "homepage": "https://github.com/gaearon/redux#readme",
+  "dependencies": {
+    "humps": "^0.6.0",
+    "isomorphic-fetch": "^2.1.1",
+    "lodash": "^3.10.1",
+    "normalizr": "^0.1.3",
+    "react": "^0.13.3",
+    "react-redux": "^0.8.0",
+    "react-router": "^1.0.0-beta3",
+    "redux": "^1.0.0-rc",
+    "redux-thunk": "^0.1.0"
+  },
+  "devDependencies": {
+    "babel-core": "^5.6.18",
+    "babel-loader": "^5.1.4",
+    "react-hot-loader": "^1.2.7",
+    "webpack": "^1.9.11",
+    "webpack-dev-server": "^1.9.0"
+  }
+}
diff --git a/examples/real-world/reducers/index.js b/examples/real-world/reducers/index.js
new file mode 100644
index 0000000000..125a0f5c4e
--- /dev/null
+++ b/examples/real-world/reducers/index.js
@@ -0,0 +1,52 @@
+import * as ActionTypes from '../actions';
+import merge from 'lodash/object/merge';
+import paginate from './paginate';
+import { combineReducers } from 'redux';
+
+/**
+ * Updates an entity cache in response to any action with response.entities.
+ */
+export function entities(state = { users: {}, repos: {} }, action) {
+  if (action.response && action.response.entities) {
+    return merge({}, state, action.response.entities);
+  }
+
+  return state;
+}
+
+/**
+ * Updates error message to notify about the failed fetches.
+ */
+export function errorMessage(state = null, action) {
+  const { type, error } = action;
+
+  if (type === ActionTypes.RESET_ERROR_MESSAGE) {
+    return null;
+  } else if (error) {
+    return action.error;
+  }
+
+  return state;
+}
+
+/**
+ * Updates the pagination data for different actions.
+ */
+export const pagination = combineReducers({
+  starredByUser: paginate({
+    mapActionToKey: action => action.login,
+    types: [
+      ActionTypes.STARRED_REQUEST,
+      ActionTypes.STARRED_SUCCESS,
+      ActionTypes.STARRED_FAILURE
+    ]
+  }),
+  stargazersByRepo: paginate({
+    mapActionToKey: action => action.fullName,
+    types: [
+      ActionTypes.STARGAZERS_REQUEST,
+      ActionTypes.STARGAZERS_SUCCESS,
+      ActionTypes.STARGAZERS_FAILURE
+    ]
+  })
+});
diff --git a/examples/real-world/reducers/paginate.js b/examples/real-world/reducers/paginate.js
new file mode 100644
index 0000000000..05c682edd5
--- /dev/null
+++ b/examples/real-world/reducers/paginate.js
@@ -0,0 +1,64 @@
+import merge from 'lodash/object/merge';
+import union from 'lodash/array/union';
+
+/**
+ * Creates a reducer managing pagination, given the action types to handle,
+ * and a function telling how to extract the key from an action.
+ */
+export default function paginate({ types, mapActionToKey }) {
+  if (!Array.isArray(types) || types.length !== 3) {
+    throw new Error('Expected types to be an array of three elements.');
+  }
+  if (!types.every(t => typeof t === 'string')) {
+    throw new Error('Expected types to be strings.');
+  }
+  if (typeof mapActionToKey !== 'function') {
+    throw new Error('Expected mapActionToKey to be a function.');
+  }
+
+  const [requestType, successType, failureType] = types;
+
+  function updatePagination(state = {
+    isFetching: false,
+    nextPageUrl: undefined,
+    pageCount: 0,
+    ids: []
+  }, action) {
+    switch (action.type) {
+    case requestType:
+      return merge({}, state, {
+        isFetching: true
+      });
+    case successType:
+      return merge({}, state, {
+        isFetching: false,
+        ids: union(state.ids, action.response.result),
+        nextPageUrl: action.response.nextPageUrl,
+        pageCount: state.pageCount + 1
+      });
+    case failureType:
+      return merge({}, state, {
+        isFetching: false
+      });
+    default:
+      return state;
+    }
+  }
+
+  return function updatePaginationByKey(state = {}, action) {
+    switch (action.type) {
+    case requestType:
+    case successType:
+    case failureType:
+      const key = mapActionToKey(action);
+      if (typeof key !== 'string') {
+        throw new Error('Expected key to be a string.');
+      }
+      return merge({}, state, {
+        [key]: updatePagination(state[key], action)
+      });
+    default:
+      return state;
+    }
+  };
+}
diff --git a/examples/real-world/server.js b/examples/real-world/server.js
new file mode 100644
index 0000000000..ff92aa06b6
--- /dev/null
+++ b/examples/real-world/server.js
@@ -0,0 +1,18 @@
+var webpack = require('webpack');
+var WebpackDevServer = require('webpack-dev-server');
+var config = require('./webpack.config');
+
+new WebpackDevServer(webpack(config), {
+  publicPath: config.output.publicPath,
+  hot: true,
+  historyApiFallback: true,
+  stats: {
+    colors: true
+  }
+}).listen(3000, 'localhost', function (err) {
+  if (err) {
+    console.log(err);
+  }
+
+  console.log('Listening at localhost:3000');
+});
diff --git a/examples/real-world/store/createAsyncExampleStore.js b/examples/real-world/store/createAsyncExampleStore.js
new file mode 100644
index 0000000000..7f8d4f6ea7
--- /dev/null
+++ b/examples/real-world/store/createAsyncExampleStore.js
@@ -0,0 +1,17 @@
+import { createStore, applyMiddleware, combineReducers } from 'redux';
+import thunkMiddleware from 'redux-thunk';
+import apiMiddleware from '../middleware/api';
+import * as reducers from '../reducers';
+
+const reducer = combineReducers(reducers);
+const createStoreWithMiddleware = applyMiddleware(
+  thunkMiddleware,
+  apiMiddleware
+)(createStore);
+
+/**
+ * Creates a preconfigured store for this example.
+ */
+export default function createAsyncExampleStore(initialState) {
+  return createStoreWithMiddleware(reducer, initialState);
+}
diff --git a/examples/real-world/webpack.config.js b/examples/real-world/webpack.config.js
new file mode 100644
index 0000000000..2061e19a48
--- /dev/null
+++ b/examples/real-world/webpack.config.js
@@ -0,0 +1,38 @@
+var path = require('path');
+var webpack = require('webpack');
+
+module.exports = {
+  devtool: 'eval',
+  entry: [
+    'webpack-dev-server/client?http://localhost:3000',
+    'webpack/hot/only-dev-server',
+    './index'
+  ],
+  output: {
+    path: path.join(__dirname, 'dist'),
+    filename: 'bundle.js',
+    publicPath: '/static/'
+  },
+  plugins: [
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NoErrorsPlugin()
+  ],
+  resolve: {
+    alias: {
+      'redux': path.join(__dirname, '..', '..', 'src')
+    },
+    extensions: ['', '.js']
+  },
+  module: {
+    loaders: [{
+      test: /\.js$/,
+      loaders: ['react-hot', 'babel'],
+      exclude: /node_modules/,
+      include: __dirname
+    }, {
+      test: /\.js$/,
+      loaders: ['babel'],
+      include: path.join(__dirname, '..', '..', 'src')
+    }]
+  }
+};
diff --git a/examples/todomvc/index.html b/examples/todomvc/index.html
index ce8e8222e9..3a5d65bf3e 100644
--- a/examples/todomvc/index.html
+++ b/examples/todomvc/index.html
@@ -1,6 +1,6 @@
 <html>
   <head>
-    <title>Redux TodoMVC</title>
+    <title>Redux TodoMVC example</title>
   </head>
   <body>
     <div class="todoapp" id="root">
diff --git a/examples/todomvc/package.json b/examples/todomvc/package.json
index fbe0328dcc..be44925d67 100644
--- a/examples/todomvc/package.json
+++ b/examples/todomvc/package.json
@@ -1,7 +1,7 @@
 {
-  "name": "todomvc",
+  "name": "redux-todomvc-example",
   "version": "0.0.0",
-  "description": "TodoMVC example for redux",
+  "description": "Redux TodoMVC example",
   "main": "server.js",
   "scripts": {
     "start": "node server.js",