A bookstore app with React 18, Vite, and Netlify functions

An in-depth guide to exploring these robust programs

Photo by Cristiano Firmani on Unsplash

React emerged back in 2011 at Facebook (now Meta) and has continued evolving rapidly and gaining a significant ecosystem with a wide variety of plugins and UI frameworks on top of it.

This guide will cover in detail the steps to create a working example bookstore single-page application using the latest React 18 and run it using the Vite. It also includes details on adding a custom state management solution and routing using the wouter library.

The present article is the React equivalent of the Vue 3+Vite article, which we’ve published recently on this blog. The result is the same for both (a bookstore single-page app which we’ve called Middlemarch). However, this tutorial also contains a backend solution using serverless functions.

Here’s the main tasks that we’ll focus on throughout this tutorial:

  1. bootstrap the application skeleton with Vite
  2. manage routes with the wouter library
  3. create an internal state management tool
  4. create and run netlify functions
  5. write React component tests with Nightwatch
  6. write and run automated end-to-end tests with Nightwatch
  7. build and deploy the application with Github Actions

This is a large undertaking, but it accurately describes the whole development cycle. The application will be deployed to Netlify. If you’re eager to get right down to coding and you’d like to jump into it right away, you can just get the project up and running with:

git clone
cd middlemarch-react
npm install
npm start

Or fork the project at


We will use the create-vite CLI to bootstrap the basic structure and dependencies.

npm create vite middlemarch -- --template react

Then follow the instructions that appear at the end, e.g.:

Scaffolding project in /home/projects/middlemarch...Done. Now run:  cd middlemarch
npm install
npm run dev

The folder structure will likely look similar to:

├── src/
| ├── App.css
| ├── App.jsx
| ├── favicon.ico
| ├── index.css
| ├── logo.svg
| └── main.jsx
├─── .gitignore
├─── index.html
├─── package.json
└─── vite.config.js

We don’t need the App.css and logo.svg, so you may delete them right away. The last thing we need to do is replace default scripts with these:

"scripts": {
"vite:start": "vite",
"vite:build": "vite build"

We have added the vite: prefix to distinguish the front-end build part from the serverless functions. All about it later.

At this point, we have prepared the development environment. Let’s dig into React and update the App.jsxfile to match the following:

StrictMode highlights potential problems in the application, which you may not notice. It’s the good practice to wrap the whole application in it.

Now, run the npm vite:start script to get the application running at http://localhost:3000.

npm run vite:start

At this point, you will see an empty page with the <main> element under the #root. The next step is to write pages with the actual content to see something in the browser.

It’s time to create our application’s main routes. In React, every page is just a separate component. For this application, we’ll consider the following components:

  • Home — the very first page that will be served at the / base URL
  • Cart — a page that contains the list of products; it is available only to logged-in users
  • Sign-in — the login form
  • Register — the user sign-up form

For the sake of simplicity, I will show you a full implementation of the Home page only. But you may always go to the GitHub repository to see the final source code of the application.

The whole application will have the following markup:

Page markup

Let’s create the Header and Footer components which remain the same across every page:

mkdir src/components
mkdir src/components/Header
mkdir src/components/Footer
touch src/components/Header/index.jsx
touch src/components/Footer/index.jsx

I am using Unix’s touch command to create files and mkdir to create directories. You may use your own editor or IDE to accomplish those tasks.

In the Header/index.jsx file, add the following content:

You may notice an unknown Link tag here and the wouter import. It is one of the Navigation components provided by the wouter library and triggers navigation to the URL from the href attribute. We will cover that library soon.

And the Footer/index.jsx:

After that, we need to include them in the application entry point. Here’s the code:

The page content will be rendered inside the main element. You may notice that we declared the file extensions for the Home and Footer component files. It is useful to distinguish the local files from external dependencies, though it is completely optional and depends on only your preferences.

Wouter is a minimal router implementation for React and Preact. You already saw the Link component, which is simply the <a> element under the hood.

Now it’s time to introduce the Route component, which will render a declared component if the URL property matches the current location.pathname value.

Let’s modify the App.jsx component again and declare the routes, as shown below:

We are using yet another component from wouter — Switch, which performs exclusive routing. That means that only the first matched route will be rendered.

After that, we have to provide the default markup for each page. Here’s the code:

export default () => <div></div>;

The last thing left is the Home page. Edit the src/pages/Home/index.jsx file and add the the following code:

For now, we will omit the search functionality for brevity. You may notice the new useBooks import – it is a custom hook that will manage the global books’ data of the application. Now that we mention it, let’s proceed to the state management section.

There are plenty of state management libraries for React that have diverse ideas behind them. Each of them is worth exploring, but in this small application, we don’t need to add yet another dependency and write complex logic to store our data, which is only an array of objects that hold the available books. React already has the API, which we can use to implement it concisely.

We will use Context for globally storing the data and hooks for delivering that data to components that need it.

mkdir src/cache
touch src/cache/books.jsx

Here are the steps to do that:

  1. Create the context — not exported, and it is private to this module.

2. Create the Provider component — exported by default.

This defines the state where we are going to store the data and returns the context Provider with the value and state dispatcher. The last one, we need to dispatch state changes, though it won’t be exposed directly to the components.

3. Create the hook.
The hook uses the context and returns a fresh array and useful methods that modify the state.

The useBooks hook exports a tuple (an array of two items) where the first item is the books array and second is collection of methods which describe the available ways of interacting with the books array.

Fetching the /api/books URL errors for now. It will work when we provide the serverless function for it.

To use the books state, we need to wrap the application with the component according to the Context using rules, as you can see below:

Serverless functions is a relatively new concept where the idea is to delegate running and maintaining server-side tasks to a cloud-computing service and only focus on writing the code. Several service providers offer this feature, including Netlify, Vercel, Render, Azure, or Cloudflare.

For this example, we will use Netlify’s functions, but you can choose your favourite provider.

Here is the schema of how everything works:

Simple description of how the Netlify CLI works.

First, we have to install the Netlify CLI and create the netlify.tomlconfiguration file:

npm i -D netlify-cli
touch netlify.toml

The Netlify CLI is needed to emulate the FaaS (Function-as-a-Service) environment and test our functions. The CLI is also responsible for running the application.

We are going to adjust Netlify’s config file, so it will know how to run the Vite development server, as shown below:

Now let’s create the mock file with the data.

mkdir data
touch data/db.json

Okay, let’s move on to the functions. By default, Netlify will search for them in netlify/functions directory.

mkdir -p netlify/functions
touch netlify/functions/books.js

Edit the netlify/functions/books.js, and add the following content:

We are using the lowdb package to read from the JSON file. Of course, you can use the fs.promises.readFiledirectly and read from the file. But this is an example, and in a real-world app, you will have a database, and you will have to use a database driver, so this example shows you that it is possible.

Serverless functions allow you to implement complex behaviour like user authentication. If you check out the source code of the current application, you may see there two pages:

And the corresponding functions:

To create an account, you must pass an email and a password. The latter will be hashed on the server, and the hash will be saved into the DB. It’s a good practice to add a so-called “salt” to the hashing function, which should be private.

For that purpose, we will create the .env file containing the PASSWORD_SECRET variable, which we will use as the “salt.”

Here’s the contents of the .env.example file. For local development, rename this to .env and fill these in:

VITE_AUTH_KEY = 'your_super_secret_auth_key'
PASSWORD_SECRET = 'super_secret_password_salt'

The VITE_AUTH_KEY environment variable is used for the remember me functionality.

When running in Netlify, use the administration UI to define the environment variables above:

1*QspcbgTHgtkmf51Y gWt9g

Component testing is a type of UI testing where the component is rendered in isolation, without the rest of the app components, to verify its functionality. It’s usually a testing strategy that happens prior to the end-to-end testing step, which we’ll elaborate in the next section.


We will use Nightwatch for both component and end-to-end testing. The easiest way to add Nightwatch tests to your project is using the create-nightwatch init tool.

Just run the following command from the project’s root folder and follow the steps:

npm init nightwatch@latest

Nightwatch supports testing React component using the @nightwatch/react, which is using internally the vite-plugin-nightwatch Vite plugin.

To install it, run:

npm i -D @nightwatch/react

After you have run the npm init nightwatch command, you should have a generated nightwatch.conf.js file.

You need to edit it and let Nightwatch know that React component testing plugin is installed:

There should be a tests directory now. We can add our first component test.

mkdir tests/component
touch tests/component/basic.js

And it should have the following content:

Nightwatch.js uses the same BDD syntax as Mocha or Jest. You can even use Mocha as a test runner in Nightwatch but for simplicity we aren’t going to do that.

Because we are making the network request, we have to mock it while testing. That’s why we have to use the browser.mockNetworkResponse method to fake the response because component tests don’t involve the running server.

Also, you may notice, the /tests/component/Books.jsx path. It is a testing component we must create to be able to test components in isolation. The reason is that we are using the context to store the application’s data.

According to React’s rules, we cannot use the context if the Provider of that context is not present up in the tree. So, if we want to test some component and it is using some context through the hook (we’ve created one: useBooks), we have to wrap it with the according provider first.

Let’s create a component for testing. We are going to test the Home page.

It’s time now to run the above test in Chrome with the following command:

npx nightwatch tests/component/basic.js --env chrome

You can pass the --headless argument if you don’t want to see the opening browser (not available if using Safari).

npx nightwatch tests/component/basic.js --env chrome --headless

Component testing in React is only available at the moment for Chrome and Edge browsers.

For now, the test does nothing helpful. Let’s extend it a bit:

Here we have created a simple test that checks for the presence of elements and content on the page. We have refactored the component mounting into the before hook, so we can do the only checks in the it block.

We have to mock the /api/books network request while testing to ensure reliable results from the server-side component.

That’s why we have used the browser.mockNetworkResponse method in that Nightwatch provides to fake the response because component tests don’t involve the running server.

End-to-End testing (E2E) helps with validating the most important flows in your application. Fortunately, we don’t need to install additional tools to implement E2E tests since we already have Nightwatch in our project (see the previous step for installation instructions).

Nightwatch can run tests against all major browsers thanks to its integration with the W3C Webdriver API and Selenium. It also allows you to use distributed cloud testing platforms like BrowserStack, SauceLabs, CrossBrowserTesting, or LambdaTest.

For running end-to-end tests you should build the application first and then serve it with the local Vite server. This is required to emulate the production environment as close as possible.

To build and run the application, use the netlify local dev server:

npx netlify dev

or simply:

npm start
mkdir tests/e2e
touch tests/e2e/HomePage.js

The test verifies whether the page is displayed and contains the book information that we’ve seen before.

Run the test by using the following command:

npx nightwatch tests/e2e/HomePage.js --env firefox

You can use another browser or several browsers if you want. Just make sure that you have the according drivers installed.

Run the test in Firefox, Chrome, and Safari in parallel. Here’s the code:

npx nightwatch tests/e2e/HomePage.js --env firefox,chrome,safari

We are almost done. The last thing left is adding CI to run the tests against the next changes automatically.

What we need to do is to create the main.yamlfile inside the .github/workflows directory with the following content.

We’ll use Xvfb to run the tests in Chrome inside the Github Actions workflow.

And we are done! GitHub Actions will run tests in three separate environments on every pull request.

If you want to dive deeper, you can go to the and explore the code or clone and run it locally.

You can also visit the and see the result.

Middlemarch React Netlify app.

For support with everything Nightwatch related, you may visit the following channels:

News Credit

%d bloggers like this: