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

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

Complement your React-app with Express.js and DynamoDB TL;DR: Infrastructure-Components let you create, start, and deploy a full-stack React-app easily. With these React-components, you can concentrate on writing the business logic of your app. You don't need to bother with its configuration.

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:

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

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

The essential JavaScript concepts that you should understand

The essential JavaScript concepts that you should understand - For successful developing and to pass a work interview

What is ECMAScript and How is it Different From JavaScript?

Many times developers use JavaScript and ECMAScript synonymously for each other. Though they are very closely linked to each other, it does not mean they are the same thing.here is a complete story on the history of JavaScript and how it came into existence. To cut the long story short, ECMA in ECMAScript refers to Europen Computer Manufacturers Association to which JavaScript 1.1 was submitted for standardization back in the year 1997.

Data Types In JavaScript

JavaScript data types are kept easy. While JavaScript data types are mostly similar to other programming languages; some of its data types can be unique. Here, we’ll outline the data types of JavaScript.

Forms of Composition in JavaScript and React

One of the core ideas in functional programming is composition: building larger things from smaller things. The canonical example of this idea should be familiar with legos.

JavaScript Memory Management System

The main goal of this article is help to readers to understand that how memory management system performs in JavaScript. I will use a shorthand such as GC which means Garbage Collection. When the browsers use Javascript, they need any memory location to store objects, functions, and all other things. Let’s deep in dive that how things going to work in GC.