How to Build and Deploy a Full-Stack React-App

Getting Started

You can set up your project in three ways:

Once you installed the dependencies (run npm install if you cloned or downloaded the files), you can build the project with a single command: npm run build

.The build script adds three more scripts to the package.json. They are ready to use!

Define the Architecture of Your App

Infrastructure-Components-based projects have a clear structure. You have a single top-level component. This defines the overall architecture of your app. Sub-components (children) refine the app’s behavior and add functions.

In the following example, the <ServiceOrientedApp/>-component is our top-level component. We export it as default in our entry point file (src/index.tsx).

export default (
	    <ServiceOrientedApp
	        stackName = "soa-dl"
	        buildPath = 'build'
	        region='eu-west-1'>
	

	        <Environment name="dev" />
	

	        <Route
	            path='/'
	            name='My Service-Oriented React App'
	            render={()=><DataForm />}
	        />
	

	        <DataLayer id="datalayer">
	

	            <UserEntry />
	            <GetUserService />
	            <AddUserService />
	

	        </DataLayer>
	

	    </ServiceOrientedApp>
	);

index.tsx

These components are all we need to build our full-stack app. As you can see, our exemplary app has

The structure of components provides a clear overview of your app. The bigger your app gets, the more important this is.

You might have noticed that the <Service/>s are children of the <DataLayer/>. This has a simple reason. We want our services to have access to the database. It really is that easy!

Database Design

The <DataLayer/> creates an Amazon DynamoDB. This is a key-value database (NoSQL). It delivers high performance at any scale. But unlike relational databases, it does not support complex queries.

The schema of the database has three fields: primaryKey, rangeKey and data. This is important because you need to know that you can only find entries through its keys. Either the primaryKey, the rangeKey, or both.

With this knowledge, let’s have a look at our <Entry/>.

export const USER_ENTRY_ID = "user_entry";
	export default function UserEntry (props)  {
	    return <Entry
	        id={ USER_ENTRY_ID }
	        primaryKey="username"
	        rangeKey="userid"
	        data={{
	            age: GraphQLString,
	            address: GraphQLString
	        }}
	    />
	};

user-entry.tsx

The <Entry/> describes the structure of our data. We define names for our primaryKey and rangeKey. You can use any name except for some DynamoDB keywords that you can find here. But the names we use have functional implications:

In our example, this means that:

Add Items to the Database

We defined two <Service/>-components in our <ServiceOrientedApp/>. A POST-service that adds a User to the database and a GET-service that retrieves a User from it.

Let’s start with the <AddUserService/>. Here’s the code of this service. We’ll go through it right away.

import * as React from 'react';
	import {
	    callService,
	    Middleware,
	    mutate,
	    Service,
	    serviceWithDataLayer
	} from "infrastructure-components";
	

	import { USER_ENTRY_ID, IUserEntry } from './user-entry';
	

	const ADDUSER_SERVICE_ID = "adduser";
	

	export default function AddUserService () {
	

	    return <Service
	        id={ ADDUSER_SERVICE_ID }
	        path="/adduser"
	        method="POST">
	

	        <Middleware
	            callback={serviceWithDataLayer(async function (dataLayer, req, res, next) {
	

	                const parsedBody: IUserEntry = JSON.parse(req.body);
	

	                await mutate(
	                    dataLayer.client,
	                    dataLayer.setEntryMutation(USER_ENTRY_ID, parsedBody)
	                );
	

	                res.status(200).set({
	                    "Access-Control-Allow-Origin" : "*", // Required for CORS support to work
	                }).send("ok");
	

	            })}/>
	

	    </Service>
	};

add-user-service.tsx

The <Service/>-component takes three parameters.

We add a <Middleware/> as a child. This <Middleware/> takes a callback function as a parameter. We could directly provide an Express.js-middleware here. For we want to access the database, we wrap the function into serviceWithDataLayer. This adds dataLayer as the first parameter to our callback.

The dataLayer provides access to the database. Let’s see how!

The asynchronous function mutate applies changes to the data in our database. It requires a client and a mutation-command as parameters.

The data of the item is a Javascript object that has all the required key-value pairs. In our service, we get this object passed from the request body (we cover this in a minute). For the User, the object has the following structure:

export interface IUserEntry {
	    username: string,
	    userid: string,
	    age: string,
	    address: string
	}

IUserEntry.tsx

This object takes the names of the primaryKey and the rangeKey and all the keys of the data that we defined in the <Entry/>.

Note: as of now, the only supported type is *string* that corresponds to*GraphQLString** in the *<Entry/>*‘s definition.*

We mentioned above that we take the IUserEntry-data from the body. How does it get there?

Infrastructure-Components provide the async function callService(serviceId, dataObject). This function takes the service-id, a Javascript-object (to be sent as the request body when using POST), a success-, and an error callback function.

The following snippet shows how we use this function to call our <AddUserService/>. We specify the service-id. And we pass through the userData that we take as a parameter for our function.

export async function callAddUserService (userData: IUserEntry) {
	

	    await callService(
	        ADDUSER_SERVICE_ID,
	        userData,
	        (data: any) => {
	            console.log("received data: ", data);
	        },
	        (error) => {
	            console.log("error: " , error)
	        }
	    );
	

	};

call-add-user-service.tsx

Now, the callAddUserService-function is all we need when we want to add a new user. For instance, call it when the user clicks a button:

<button onClick={() => callAddUserService({
	    username: username,
	    userid: userid,
	    age: age,
	    address: address
	})}>Save</button>

add-user.tsx

We just call it with an IUserEntry-object. It calls the right service (as specified by its id). It puts the userData into the body of the request. The <AddUserService/> takes the data from the body and puts it into the database.

Retrieve Items from the Database

Retrieving items from the database is as easy as adding them.

export default function GetUserService () {
	    return <Service
	        id={ GETUSER_SERVICE_ID }
	        path="/getuser"
	        method="GET">
	

	        <Middleware
	            callback={serviceWithDataLayer(async function (dataLayer, req, res, next) {
	            
	                const data = await select(
	                    dataLayer.client,
	                    dataLayer.getEntryQuery(USER_ENTRY_ID, {
	                        username: req.query.username,
	                        userid: req.query.userid
	                    })
	                );
	

	                res.status(200).set({
	                    "Access-Control-Allow-Origin" : "*", // Required for CORS support to work
	                }).send(JSON.stringify(data));
	

	            })}/>
	

	    </Service>
	}

get-user-service.tsx

Again, we use a <Service/>, a <Middleware/> and a callback-function with database access.

Instead of the mutate-function that adds an item to the database, we use the select-function. This function requires the client that we take from the dataLayer. The second parameter is the select-command. Like a mutation-command, we can create a select-command with the help of the dataLayer.

This time, we use the getEntryQuery-function. We provide the id of the <Entry/> whose item we want to get. And we provide the keys (primaryKeyand rangeKey) of the specific item in a Javascript object. For we provide both keys, we get a single item back. If it exists.

As you might have seen, we take the key-values from the request. But this time, we take them from the request.query rather than from the request.body. The reason is that this service uses the GET-method. This method does not support a body in the request. But it provides all the data as query parameters.

The callService function handles that for us. Like in the callAddUserService-function, we provide the id of the <Service/> we want to call. We provide the required data. Here it is only the keys. And we provide callback functions.

The success-callback provides the response. The json-formatted body of the response contains our retrieved item. We can access this item through the key get*user*entry. “get_” specifies the query we have put into our selectfunction. “user_entry” is the key of our <Entry/>.

export async function callGetUserService (username: string, userid: string, onData: (userData: IUserEntry) => void) {
	

	    await callService(
	        GETUSER_SERVICE_ID,
	        {
	            username: username,
	            userid: userid
	        },
	        async function (response: any) {
	            await response.json().then(function(data) {
	                console.log(data[`get_${USER_ENTRY_ID}`]);
	                onData(data[`get_${USER_ENTRY_ID}`]);
	            });
	        },
	        (error) => {
	            console.log("error: " , error)
	        }
	    );
	

	}

call-get-user-service.tsx

See Your Full-Stack App in Action

If you have not started your app yet, it’s a good time to do it now: npm run start-{your-env-name}.

You can even deploy your app to AWS with a single command: npm run deploy-{your-env-name}. (Don’t forget to put the AWS credentials into the .env-file).

This post does not cover how you enter the data you put into the database and how you display the results. callAddUserService and callGetUserService encapsulate everything that is specific to the services and the database. You just put a Javascript object in there and get it back.

You’ll find the source code of this example in this GitHub-repository. It includes a very basic user interface.

Further reading:

The Image Processing Tutorial from Zero to One

React Native Tutorial for Beginners - Crash Course 2019

Vuejs is Good ! But Is It Better Than Angular or React?

An Intro to Redux that you can understand

Setting React - Laravel Without using Laravel mix

How to set up a TypeScript + Gatsby app

Getting started with react-select

Understanding State and Props in ReactJS

#reactjs #javascript

How to Build and Deploy a Full-Stack React-App
3 Likes29.25 GEEK