A Web Application Journey, part 8: scratch-cooking a recipes app

This continues on from part 7, and is part of the Web Application Journey series. To this point, we have some technology, but no real application to speak of, so we’ll quickly sketch out some functionality to provide a larger base upon which we can introduce other techniques.

Let’s build a simple cooking recipe manager. This is basically a CRUD application, but if we want to, it would make sense to introduce some offline and/or mobile (i.e. React Native) use cases.

Application sketch

We’ll initially set up our domain model without authentication and limit our domain data model to simply “Recipe.” (We’ll likely introduce Users later as that introduces some interesting use cases.)

To begin with, we’ll use a simple scheme for our URLs (i.e., pages/places/routes):

/                                        [HomePage]
/about [AboutPage]
/recipes?(criteria) [FilterRecipesPage]
/recipe/{recipeId}/{recipeSlugFromName} [ViewRecipePage]
/r/edit/{recipeId} [EditRecipePage]

We’ll quickly build up these pages and routes and get some dummy data setup and then discuss the relevant considerations/challenges and next steps.

Scratch implementation

Feature-based directory layout and index files

Before we start, let’s quickly discuss introducing a more scalable directory structure for the UI (client and server) aspects of the project. We’ll use a feature-based grouping so have the following:

src
/ui - elements for the user interface
/site - elements pertaining to the overall site design
/recipe - elements pertaining to the "recipe" domain

So “site” will include AppLayout, HomePage, and AboutPage, as well as some of the general components like Header and Footer. We’ll export the containers/layouts via an index.js (see later).

Similarly, “recipe” will include FilterRecipesPage and similar containers, and any recipe-related components required and the index.js will be as follows:

export FilterRecipesPage from './FilterRecipesPage'
export ViewRecipePage from './ViewRecipePage'
export EditRecipePage from './EditRecipePage'

This uses a (currently) EcmaScript Stage 1 proposal for that “export … from” syntax. We’ll (somewhat dangerously!) pull in all such Stage 1 (and higher) proposed features:

npm install --save-dev babel-preset-stage-1

This preset then needs to be referenced in our distinct client and server-side builds via webpack.config.js and package.js alongside the es2015[-node] and react presets.

Route configuration

Our “routes.js” file needs to be updated as follows, importing from the modules’ index.js files.

// ... other imports
import {AppLayout, HomePage, AboutPage} from './ui/site'
import {FilterRecipesPage, ViewRecipePage, EditRecipePage} from './ui/recipe'
export default (
<Route path="/" component={AppLayout}>
<IndexRoute component={HomePage} />
<Route path="about" component={AboutPage} />
<Route path="recipes" component={FilterRecipesPage} />
<Route path="recipe/:recipeId(/:recipeSlug)" component={ViewRecipePage} />
<Route path="r/edit/:recipeId" component={EditRecipePage} />
</Route>
)

I like how compact the JSX-style for react-router is, and how similar it is to our “application sketch” at the beginning of this article.

Page component definitions

We’ll quickly rough-in those new recipe-related Page components using the stateless function syntax, e.g. for the ViewRecipePage:

import React from 'react'
import Helmet from 'react-helmet'
import Link from 'react-router/lib/Link'
export default ({params: {recipeId, recipeSlug}}) => (
<div>
<Helmet title='Recipe: SOME RECIPE NAME' />
<p>
We should view recipe with ID {recipeId}
{recipeSlug ? ` and slug '${recipeSlug}' ` : ' '}
here
</p>
<Link to={`/r/edit/${recipeId}`}>Edit this Recipe</Link>
</div>
)

In the above, we’ve used the ES2015 object de-structuring syntax to capture the recipeId param that react-router has passed in via the component’s “param” property. “recipeSlug” is optional, so might end up being undefined.

Next steps

So we’ve got our routes working and the pages are “there” but they don’t contain any realistic data. Running “npm run go” shows that the routing is working for client and server (we’ve added a dummy recipe in the home page), but that’s it. So, we need a way to take the URL parameters (on either the client or the server) and fetch the data, then complete the render. Fetching data should be handled asynchronously, and that presents some challenges on both the client and the server.

The other thing we need to do soon is improve the development experience in a number of ways. Having to stop, re-build, and re-run the client and server when we make minor source code changes is getting old (again!).