React Native MasteryThe Ultimate React Native and Expo Course. Launching November 26

Nike App Backend: node.js, MongoDB, Redux Toolkit Query

In this tutorial we will build a backend for an e-commerce application - Nike app.

The backend will be a REST API build with NodeJS and Express.

For the database, we will use MongoDB.

In the end, we will query our API from our React Native applicaiton using Redux Toolkit Query.

This guide is intended to be followed alongside the video tutorial

Get yourself a cup of coffee, and let’s start learning new stuff by building exciting projects.

Build a REST API with NodeJS

Functional requirements:

Products:

  • Get a list of products
  • Get one product

Orders:

  • Create order
  • Get order by reference number

Non functional requirements

  • NodeJS backend with express
  • MongoDB for the database
  • Run the server locally

NodeJS server

Start by creating a new directory for our backend project, and then open it with VSCode.

Initialize an empty project

BASH
npm init -y

Install nodemon as a dev dependency. Nodemon will automatically restart our server when we do some changes.

BASH
npm i -D nodemon

Install express

BASH
npm i express

Finally, let’s create our index.js inside the src file, that will be the entry point of our backend.

BASH
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send("<h2>Hello world!</h2>");
});
app.listen(PORT, () => {
console.log('API is listening on port ', PORT);
});

Add the dev script in package.json

JSON
"scripts": {
...
"dev": "nodemon src/index.js"
},

Run the server by executing the npm run dev command, and then visit http://localhost:3000/ to see our first page.

Products CRUD

Router

Create the productRoutes.js file

JAVASCRIPT
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.send('Get all products');
});
router.get('/:productId', (req, res) => {
res.send(`Get product with id: ${req.params.productId}`);
});
module.exports = router;

And then, connect the product router to the main application inside src/index.js

JAVASCRIPT
app.use('/products', productRoutes);

Database

We will use MongoDB as our database.

Let’s create it on https://www.mongodb.com/, which offers a free plan for hosting your database in the cloud.

  • Create an account and sign in

  • Create a Project

  • Create a database

    Untitled.png

  • Create a database user and whitelist your local ip address

  • Create the products collection, and populate the collection with the data from the asset bundle (code/data/products.json)

Connect to Mongodb from NodeJS

Install the mongodb driver

BASH
npm install mongodb@5.1

Connect to our mongodb database, from src/database/db.js

JAVASCRIPT
const { MongoClient } = require('mongodb');
const uri =
'mongodb+srv://<usern>:<passoword>@cluster0.dvnz6mb.mongodb.net/?retryWrites=true&w=majority';
const client = new MongoClient(uri);
const database = client.db('test');
const products = database.collection('products');
module.exports = {
products,
};

Add the database interaction logic inside src/database/products.js

JAVASCRIPT
const { ObjectId } = require('mongodb');
const db = require('./db');
const getAllProducts = async () => {
return await db.products.find().toArray();
};
const getProduct = async (id) => {
return await db.products.findOne({ _id: new ObjectId(id) });
};
module.exports = {
getAllProducts,
getProduct,
};

Let’s also update our productRoutes to use the database layer

JAVASCRIPT
const express = require('express');
const router = express.Router();
const { getAllProducts, getProduct } = require('../database/products');
router.get('/', async (req, res) => {
const products = await getAllProducts();
res.send({ status: 'OK', data: products });
});
router.get('/:productId', async (req, res) => {
const product = await getProduct(req.params.productId);
if (!product) {
res.status(404).send({ status: 'FAILED', error: 'Product not found' });
return;
}
res.send({ status: 'OK', data: product });
});
module.exports = router;

Now, if you access the http://localhost:3000/products/ you should see a list of products coming from our database.

Orders CRUD

Let’s start by creating the router src/routes/orderRoutes.js

JAVASCRIPT
const express = require('express');
const router = express.Router();
router.get('/:reference', (req, res) => {
res.send(`Get order ${req.params.reference}`);
});
router.post('/', (req, res) => {
res.send('Creating an order');
});
module.exports = router;

And connect it with our main app in src/index.js

Orders database layer

Create and export the collection inside src/database/db.js

JAVASCRIPT
...
const orders = database.collection('orders');
module.exports = {
products,
orders,
};

Write the logic for the orders database layer in src/database/orders.js

JAVASCRIPT
const db = require('./db');
const getOrder = async (ref) => {
return await db.orders.findOne({ ref });
};
const createOrder = async (order) => {
const result = await db.orders.insertOne(order);
return { ...order, _id: result.insertedId };
};
module.exports = {
getOrder,
createOrder,
};

Now, let’s call the database layer from our router:

Install the body-parser package that will parse the incoming data and transform it to json

JAVASCRIPT
npm i body-parser

Add it to our app inside src/index.js

JAVASCRIPT
app.use(bodyParser.json());

Let’s send a post request using curl form our terminal. If you have an HTTP client like Postman, you can use it.

JAVASCRIPT
curl -X POST -H "Content-Type: application/json" \
-d "{\"items\":[{\"product\":{\"id\":\"1\",\"image\":\"https://notjustdev-dummy.s3.us-east-2.amazonaws.com/nike/nike1.png\",\"name\":\"Wild Berry\",\"price\":160},\"size\":42,\"quantity\":2},{\"product\":{\"id\":\"2\",\"image\":\"https://notjustdev-dummy.s3.us-east-2.amazonaws.com/nike/nike2.png\",\"name\":\"Air Force 1\",\"price\":169},\"size\":43,\"quantity\":1},{\"product\":{\"id\":\"3\",\"image\":\"https://notjustdev-dummy.s3.us-east-2.amazonaws.com/nike/nike3.png\",\"name\":\"Nike Cosmic\",\"price\":129},\"size\":44,\"quantity\":1}],\"subtotal\":450,\"deliveryFee\":15,\"total\":465,\"customer\":{\"name\":\"Vadim Savi\",\"email\":\"vadim@notjust.dev\",\"address\":\"26985 Brighton Lane, Lake Forest, CA\"}}" \
http://localhost:3000/orders/

Order routes

JAVASCRIPT
const express = require('express');
const router = express.Router();
const { createOrder, getOrder } = require('../database/orders');
router.get('/:reference', async (req, res) => {
const order = await getOrder(req.params.reference);
if (!order) {
res.status(404).send({ status: 'FAILED', error: 'Order not found' });
return;
}
res.send({ status: 'OK', data: order });
});
router.post('/', async (req, res) => {
const orderData = req.body;
const ref = (Math.random() + 1).toString(36).substring(7);
orderData.ref = ref;
const newOrder = await createOrder(orderData);
res.status(201).send({ status: 'OK', data: newOrder });
});
module.exports = router;

Query the REST API from React Native

For this part, we will work with the application that we have build in the first part of the series Building the Ultimate Nike App in React Native & Redux.

The source code for the UI can be found here.

To query the data from our API inside React Native, we will use the RTK Query library from Redux Toolkit.

RTK Query is provided as an optional addon within the @reduxjs/toolkit package. It is purpose-built to solve the use case of data fetching and caching, supplying a compact, but powerful toolset to define an API interface layer for your app. It is intended to simplify common cases for loading data in a web application, eliminating the need to hand-write data fetching & caching logic yourself.

Create the API Slice

Let’s start by creating a new slice for the api inside src/store/apiSlice.js

JAVASCRIPT
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const baseUrl = 'http://localhost:3000/';
// Define a service using a base URL and expected endpoints
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl }),
endpoints: (builder) => ({
getProducts: builder.query({
query: () => 'products',
}),
getProduct: builder.query({
query: (id) => `products/${id}`,
}),
}),
});
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const { useGetProductsQuery, useGetProductQuery } = apiSlice;

And add it to the main store inside src/store/index.js

JAVASCRIPT
import { configureStore } from '@reduxjs/toolkit';
import { productsSlice } from './productsSlice';
import { cartSlice } from './cartSlice';
import { apiSlice } from './apiSlice';
export const store = configureStore({
reducer: {
products: productsSlice.reducer,
cart: cartSlice.reducer,
api: apiSlice.reducer,
},
// Adding the api middleware enables caching, invalidation, polling,
// and other useful features of `rtk-query`.
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware),
});

Use hooks inside components

Now, let’s use the auto-generated hook to query the products inside src/screens/ProductsScreen.js

JAVASCRIPT
const { data, error, isLoading } = useGetProductsQuery();
if (isLoading) {
return <ActivityIndicator />;
}
return (
<FlatList
data={data.data}
...

Inside the src/screens/ProdictDetailsScreen.js we need to query the product

JAVASCRIPT
const { data, error, isLoading } = useGetProductQuery(route.params.id);
if (isLoading) {
return <ActivityIndicator />;
}
if (error) {
return <Text>{error.error}</Text>;
}
const product = data.data;

For this to work, we have to send the id of the product when we click on it inside ProductsScreen.js

JAVASCRIPT
onPress={() => {
// update selected product
// dispatch(productsSlice.actions.setSelectedProduct(item.id));
navigation.navigate('Product Details', { id: item._id });
}}

Orders API integration

Let’s add the orders endpoints to our apiSlice.js

JAVASCRIPT
createOrder: builder.mutation({
query: (newOrder) => ({
url: 'orders',
method: 'POST',
body: newOrder,
}),
}),
getOrder: builder.query({
query: (ref) => `orders/${ref}`,
}),

And use the newOrder mutation inside the ShoppingCart.js

JAVASCRIPT
const onCreateOrder = async () => {
const result = await createOrder({
items: cartItems,
subtotal,
deliveryFee,
total,
});
if (result.data?.status === 'OK') {
console.log(result.data);
Alert.alert(
'Order has been submitted',
`Your order reference is: ${result.data.data.ref}`
);
dispatch(cartSlice.actions.clear());
}
};

Add the clear cart reducer inside the cartSlice.js

JAVASCRIPT
clear: (state) => {
state.items = [];
},

Track orders

Let’s start by creating a blank screen screens/TrackOrder.js, and add it as a screen inside navigation.js

JAVASCRIPT
<Stack.Screen name="Track Order" component={TrackOrder} />

We will navigate to this page by pressing on an icon from the products page header

JAVASCRIPT
headerLeft: () => (
<MaterialCommunityIcons
onPress={() => navigation.navigate('Track Order')}
name="truck-delivery"
size={22}
color="gray"
/>
),

Now, we can implement the Track order screen. We need to have a in input where we can type our order reference, and query the api to look for the order.

JAVASCRIPT
import {
View,
Text,
TextInput,
StyleSheet,
ActivityIndicator,
} from 'react-native';
import { useState } from 'react';
import { useGetOrderQuery } from '../store/apiSlice';
const TrackOrder = () => {
const [ref, setRef] = useState('');
const { data, isLoading, error } = useGetOrderQuery(ref);
return (
<View style={styles.root}>
<TextInput
style={styles.input}
value={ref}
onChangeText={setRef}
placeholder="Your order reference"
/>
{isLoading && <ActivityIndicator />}
{data?.status !== 'OK' && <Text>Order not found</Text>}
{data?.status === 'OK' && <Text>Order: {JSON.stringify(data.data)}</Text>}
</View>
);
};
const styles = StyleSheet.create({
root: {
padding: 10,
},
input: {
borderColor: 'lightgrey',
borderWidth: 1,
padding: 10,
borderRadius: 5,
},
});
export default TrackOrder;

Conclusion

Congrats 🎉 

We have build a REST API and also integrated it inside our React Native applicaiton using Redux Toolkit Query.

I hope you learned something new today, and had fun following along.


Vadim Savin profile picture

Vadim Savin

Hi 👋 Let me introduce myself

I started my career as a Fullstack Developer when I was 16 y.o.

In search of more freedom, I transitioned to freelancing, which quickly grew into a global software development agency 🔥

Because that was not challenging enough, I started my startup which is used by over 20k users. This experience gave another meaning to being a (notJust) developer 🚀

I am also a proud ex-Amazon SDE and Certified AWS Architect, Developer and SysOps. You are in good hands 👌