Introduction
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
npm init -y
Install nodemon as a dev dependency. Nodemon will automatically restart our server when we do some changes.
npm i -D nodemon
Install express
npm i express
Finally, let’s create our index.js inside the src file, that will be the entry point of our backend.
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
"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
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
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
-
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
npm install mongodb@5.1
Connect to our mongodb database, from src/database/db.js
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
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
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
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
...const orders = database.collection('orders');module.exports = {products,orders,};
Write the logic for the orders database layer in src/database/orders.js
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
npm i body-parser
Add it to our app inside src/index.js
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.
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
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
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';const baseUrl = 'http://localhost:3000/';// Define a service using a base URL and expected endpointsexport 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 endpointsexport const { useGetProductsQuery, useGetProductQuery } = apiSlice;
And add it to the main store inside src/store/index.js
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
const { data, error, isLoading } = useGetProductsQuery();if (isLoading) {return <ActivityIndicator />;}return (<FlatListdata={data.data}...
Inside the src/screens/ProdictDetailsScreen.js we need to query the product
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
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
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
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
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
<Stack.Screen name="Track Order" component={TrackOrder} />
We will navigate to this page by pressing on an icon from the products page header
headerLeft: () => (<MaterialCommunityIconsonPress={() => 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.
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}><TextInputstyle={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.