This article focuses on implementing React based frontend application to perform CRUD operations on backend API which exposes these operations as RESTful APIs.

There are lot of online learning materials and blog posts providing greater insights on getting started with implementing React applications and those which focuses on integrating with RESTful APIs. Instead of rewriting the same, we shall focus on few concepts that needs some insights on using React Hooks, Axios Integration, Internationalization and Containerizing.

Below are the screenshots of what the application looks like when integrated with the RESTful API service.

Catalogue Listing

Adding Catalogue Item

Editing Catalogue Item with labels displayed in dutch

Delete confirmation in dutch

Technology stack used in this Article to build and test drive the application


Basic knowledge of React, Node, Docker is necessary for test driving this application.

Application referred in this article is built and tested on Ubuntu OS. If you are on windows and would like to make your hands dirty with Unix, then I would recommend going through

Configure Development environment on Ubuntu 19.10
article which has detailed steps on setting up development environment on Ubuntu.

At the least, you should have below softwares and tools installed to try out the application:

$ node -v

$ yarn -v

$ docker -v
Docker version 19.03.6, build 369ce74a3c

Bootup RESTful API needed for this integration

There is a previous article published on

Creating RESTful Microservice using Quarkus
which exposes APIs that are compatible to be used with this react application.

Microservice is built using

to expose APIs and also support handling/publishing events to Kafka. This is integrated with
for centralized logging and
for distributed tracing.

Below are the Restful APIs that are exposed by Catalogue Management System microservice application.

API NamePathResponse
Status Code
POSTCreate Catalogue Item/201
GETGet Catalogue Items/200
GETGet Catalogue Item/{sku}200
PUTUpdate Catalogue Item/{sku}200
DELETEDelete Catalogue Item/{sku}204
(No Content)

Follow the series of steps mentioned in Prerequisites section to boot up the database and supporting containers. To ensure microservice is up and running, access http://localhost:8080/health in browser and should show status as UP.

Exploring the React Application

Project is bootstrapped with

and configured with additional dependencies.


This should clone the application with below project structure and dependencies added to package.json

Project Structure

Project Dependencies

As observed, below are the dependencies added to the project which are the building blocks of this application.

Drilling through the code will give us an understanding on the implementation if you are aware of implementing simple React applications.

Of the whole, we shall focus on below aspects rather than the core concepts of React.

React Hooks

Hooks are a new addition in React 16.8. Hooks provide a more direct API to the React concepts you already know: props, state, context, refs, and life cycle. As we will show later, Hooks also offer a new powerful way to combine them.

Refer to the

for detailed understanding on React Hooks.

Below are some highlights:

useState hook allows us to use state in our functional components. A useState hook takes the initial value of our state as the only argument, and it returns an array of two elements. The first element is our state variable and the second element is a function in which we can use the update the value of the state variable.

Initialize state with current state and function to update it
const [form, setState] = useState({  sku: '',
  name: '',
  description: '',
  category: '',
  price: '',
  inventory: ''

const updateField = e => {
  setState({    ...form,

const updateCategoryChange = selectedOption => {
  setState({     ...form,
    category: selectedOption

useEffect adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API. By default, React runs the effects after every render — including the first render.

Accessing logic upon every render
const [selectedCatalogueItem, setSeletedCatalogueItem]   = useState({
    sku: '',
    name: '',
    description: '',
    category: '',
    price: '',
    inventory: ''

useEffect(() => {  const catalogueItemSku = sku;

  const selectedCatalogueItem = catalogueItems.find(catalogueItem => catalogueItem.sku === catalogueItemSku);
}, []);

createContext hook allows us to consume the value of a context. It is introduced part of the Context API. Context provides a way to pass data through the component tree without having to pass props down manually at every level.

Creating GlobalContext which will be available to all components
export const GlobalContext = createContext();

return (<GlobalContext.Provider value={{
  catalogueItems: state.catalogueItems,
  notification: state.notification,

useContext hook lets you subscribe to React context without introducing nesting.

Getting and calling context functions
const { catalogueItems, getCatalogueItem, editcatalogueItem } = useContext(GlobalContext);
const onSubmit = e => {

useReducer hook lets you manage local state of complex components with a reducer. It allows functional components in React access to reducer functions from your state management. It accepts reducer of type (state, action) => newState, and returns the current state paired with a dispatch method.

export default (state, action) => {  
  if(action.type === 'API_CALL_FAILED') {      console.log(action.payload,action.error);
      return {
          notification: prepareNotificationToDisplay("failure", action.payload)
  else if(action.type === 'GET_CATALOGUE_ITEMS') {
      //console.log("===> GET_catalogueItemS_ITEMS received", action.payload);      return {
          catalogueItems: action.payload
using reducer hook
const [state, dispatch] = useReducer(AppReducer, initialState);
function dispatchAPICallFailure(message, e) {
    dispatch({        type: 'API_CALL_FAILED',
        error: e,
        payload: message

Internationalization support

Support for internationalization is achieved with react-i18next. With simple steps, we can localize the application.

Ensure you have the below dependencies in package.json

"dependencies": {
  "i18next": "^19.4.4",
  "i18next-browser-languagedetector": "^4.1.1",
  "i18next-http-backend": "^1.0.6",
  "react-i18next": "^11.4.0",

Configure i18n

Start with configuring i18n by creating i18n.js at the root of src folder as below. This will register

making HTTP calls to load translation files from default path /locales/{{lng}}/{{ns}}.json along with
which detects user language in the browser by many different means.

Observing the init block, The fallback language is set to en and debug to true which will log console messages which will help us to track any missing translation messages in language files.

import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

  // load translation using http -> see /public/locales
  // learn more:
  // detect user language
  // learn more:
  // pass the i18n instance to react-i18next.
  // init i18next
  // for all options read:
    fallbackLng: 'en',    debug: true,

    interpolation: {
      escapeValue: false, // not needed for react as it escapes by default

export default i18n;

To bundle this to the application, Import i18n.js in index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './stylesheet/styles.css';
import App from './App';

// import i18n (needs to be bundled ;))
import './i18n';
        <App />

Translate the application

As mentioned earlier, translation messages are loaded from Backend with default path for loading the translation language files from /locales/{{lng}}/{{ns}}.json. To get this working, create locales file under public folder and start creating folders for languages that are being supported with translation json files.

Below is sample for translation.json from en and de language folders:

Translation files

English Translation
  "title": "Catalogue Items Listing",
  "controls": {
      "add": "Add Catalogue Item",
      "addItem": "Add Item",
      "editItem": "Edit Item",

      "cancel": "Cancel"
  "nodata": "No Data",
  "form": {
      "sku": "SKU",
      "name": "Name",
      "description": "Description",
      "category": "Category",
      "price": "Price",
      "inventory": "Inventory"
Dutch Translation
  "title": "Catalogusitems",
  "controls": {
      "add": "Catalogusitem toevoegen",
      "addItem": "Voeg item toe",
      "editItem": "Item bewerken",

      "cancel": "annuleren"
  "nodata": "Geen informatie",
  "form": {
      "sku": "SKU",
      "name": "Naam",
      "description": "Omschrijving",
      "category": "Categorie",
      "price": "Prijs",
      "inventory": "Voorraad"

Changing language

Language can be changed by detecting for the change with i18next-browser-languageDetector or can be changed with user action.

Navigate to Heading.js and observe how UI controls are added to switch language.

export const Heading = () => {

  const { t, i18n } = useTranslation();  const { notification } = useContext(GlobalContext);
  const [notificationToDisplay, setNotificationToDisplay] 
      = useState({
          display: false,
          message: ''
  const [language, setLanguage] = useState('en');
  const changeLanguage = lng => {      i18n.changeLanguage(lng);

  useEffect(() => {
  }, [notification, i18n.language]);

  return (
      <div className="mt-5">
          <button className={(language === 'en' ? 'bg-blue-500' : 'bg-blue-200') + ` hover:bg-blue-700 text-white font-bold py-2 px-4 rounded m-2`}              onClick={() => changeLanguage('en')}>English</button>
          <button className={(language === 'de' ? 'bg-blue-500' : 'bg-blue-200') + ` hover:bg-blue-700 text-white font-bold py-2 px-4 rounded`}              onClick={() => changeLanguage('de')}>Dutch</button>

As highlighted, language state is defined and is being set in useEffect which will be invoked upon rendering the component and also upon onClick action. Based on language value, background of the button is changed resulting in below output when passing language as query param http://localhost:3000/?lng=de or click on language button.

When language is set to English

When language is set to Dutch

API Integration

Axios is the http client used to integrate with backend API. Axios is promise-based library which uses

async and await
for implementing asynchronous code.

Axios allows us to define a base instance with which we can define a URL and any other configuration elements. http-common.js exports a new axios instance with these configuration details.

import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:8080/api/v1",  headers: {
    "Content-type": "application/json"  }

API integrations to catalogue management backend service are defined in CatalogueService.js and called upon from context provider functions in GlobalState.js. Below is the sample for getting catalogue items and adding catalogue item.

const getCatalogueItems = async () => {
    await catalogueService
    .getAll()    .then(response => {
        //console.log("======> getCatalogueItems response :: ",;
            type: 'GET_CATALOGUE_ITEMS',            payload:
    .catch(e => {            
        dispatchAPICallFailure('notifications.failure.itemGetAll', e);    });

const addcatalogueItem = async (catalogueItem) => {
    state.notification.display = false;

    await catalogueService    .create(catalogueItem)
    .then(response => {
        //console.log("======> addcatalogueItem response :: ",response);
            type: 'ADD_catalogueItem',            payload: catalogueItem
    .catch(e => {
        dispatchAPICallFailure('notifications.failure.itemAdded', e);    });

Based on the response status, dispatch event is published which will be handled in AppReducer.js to display appropriate notification message.

export default (state, action) => {

  function prepareNotificationToDisplay(status, message) {      return {
          display: true,
          status: status,
          message: message
  if(action.type === 'API_CALL_FAILED') {      console.log(action.payload,action.error);
      return {
          notification: prepareNotificationToDisplay("failure", action.payload)      };
  else if(action.type === 'ADD_catalogueItem') {      //console.log("===> ADD_catalogueItem ::", state, action);
      return {
          catalogueItems: [...state.catalogueItems, action.payload],
          notification: prepareNotificationToDisplay("success", 'notifications.success.itemAdded')      };

Upon successfully adding catalogue item

Upon failure when adding catalogue item

Starting & Building the application

Ensure to install node dependencies before starting the application.

Ensure to install dependencies
$ yarn  install

Below are the scripts configured in package.json which enables us to start the application in dev mode, build and test the app.

"scripts": {
	"dev": "react-scripts start --no-cache",
	"build": "react-scripts build'",
	"test": "react-scripts test --no-cache",
  "eject": "react-scripts eject",
  "serve": "serve -s build"

Start application in dev mode. Upon successfully starting, browser should be opened with url pointing to http://localhost:3000

Start in dev mode
$ yarn dev

Run the below command to create optimized production build, which can be deployed or served using

package and accessible at http://localhost:5000.

Build and serve %the application
$ yarn build

$ yarn serve

Dockerizing React Application with Runtime environment variables

In the world of containers, applications are packed as cloud native apps and deployed to platforms which ensure apps to run efficiently and scale easily.

With different environments and their hostname changing for each deployment, it is possible to configure backend applications with service discovery services or injected during runtime when deployed to container orchestration platforms. But this is not possible for frontend applications as we cannot define environment variables inside the browser environment.

To ensure frontend apps are connected to scaling backend services, we need to inject environment variables when we start the container. Then we can read environment variables from inside the container and do appropriate actions such that the frontend app can access the backend service from browser.

Steps on configuring in the application, using it in the code and passing the Runtime Environment Variable to docker container are explained in detail in

this article

Below are brief changes done as per steps defined in the



# Recreate config file
rm -rf ./env-config.js
touch ./env-config.js
# Add assignment 
echo "window._env_ = {" >> ./env-config.js
# Read each line in .env file
# Each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
  # Split env variables by character `=`  if printf '%s\n' "$line" | grep -q -e '='; then
    varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
    varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')

  # Read value of current variable if exists as Environment variable  value=$(printf '%s\n' "${!varname}")
  # Otherwise use value from .env file
  [[ -z $value ]] && value=${varvalue}
  # Append configuration property to JS file
  echo "  $varname: \"$value\"," >> ./env-config.jsdone < .env

echo "}" >> ./env-config.js
    <script src="%PUBLIC_URL%/env-config.js"></script>
import axios from "axios";

export default axios.create({
  baseURL: window._env_.API_URL,  headers: {
    "Content-type": "application/json"
# => Build container
FROM node:alpine as builder
COPY package.json .
COPY yarn.lock .
RUN yarn
COPY . .
RUN yarn build
# => Run container
FROM nginx:1.17.10-alpine
# Nginx config
RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx

# Static build
COPY --from=builder /app/build /usr/share/nginx/html/
# Default port exposure

# Initialize environment variables into filesystem
WORKDIR /usr/share/nginx/html
COPY ./ .COPY .env .
# Add bash
RUN apk add --no-cache bash

# Run script which initializes env vars to fs
RUN chmod +x RUN ./

# Start Nginx server
CMD ["/bin/bash", "-c", "/usr/share/nginx/html/ && nginx -g \"daemon off;\""]
Create docker image and run it
$ docker build . -t narramadan/crud-react-2much2learn-shopping-app

$ docker run -p 3000:80 -e API_URL= -t narramadan/crud-react-2much2learn-shopping-app

$ docker push -t narramadan/crud-react-2much2learn-shopping-app
"scripts": {
  "dev": "chmod +x ./ && ./ && cp env-config.js ./public/ && react-scripts start --no-cache",  "build": "sh -ac '. ./.env; react-scripts build'",  "test": "react-scripts test --no-cache",
  "eject": "react-scripts eject",
  "serve": "serve -s build"


As Fullstack Developer, its ideal to get your hands dirty with both backend and frontend development and implementing CRUD application is the easy way to gain hands on knowledge on the technology stack. This is just tip of an iceberg with getting things done with React.

With capabilities added to use Runtime Environment Variables to pass backend host details, frontend application can be configured to connect to backend service running in any environment without change in code or docker image.