Building a React App With Server-Side Swift

In this tutorial, you’ll learn to create a dynamic web application with React.js and integrate it with a Vapor server-side application. By Mattia Fochesato.

Leave a rating/review
Download materials
Save for later
Share

React.js is the most popular JavaScript library used for building dynamic web apps. It is straightforward to learn, lets you quickly create complex apps, and provides good performance.

In this tutorial, you’ll see how to create MyBrary, a simple web app that manages your book library.

You’ll learn how to:

  • Develop a simple React app.
  • Make different pages with React Router.
  • Make requests to the Vapor server from React.
  • Serve the React app from Vapor.
Note: This tutorial assumes you know frontend and backend development basics. You’ll need an editor such as Visual Studio Code to work on your project. You’ll also need Swift installed on your machine to test your Vapor project.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

The starter project includes a backend application with a simple REST API that allows you to save and browse a collection of books. Open a Terminal window and navigate to the starter/backend-vapor folder. Now, run the following:

swift run

The first time you run this command, it can take a while because it has to fetch all the dependencies and compile all the code. However, after the first run, it should be fast to compile the changes you’ve made to the app. When the command finishes, you’ll see the following message:

[ NOTICE ] Server starting on http://127.0.0.1:8080

Now open a new Terminal window and enter the following command:

curl http://127.0.0.1:8080/books

This will return a JSON-formatted list of books, like this:

[
  {
    "id": "59AB6499-EB0A-4AAA-AA91-BDF0DAA4E3F1",
    "author": "Logan Wright, Tim Condon and Tanner Nelson",
    "title": "Server-Side Swift with Vapor"
  },
  {
    "id": "6F7B5287-6F8D-4324-8307-63A337BFF0A4",
    "author": "Scott Grosch",
    "title": "Concurrency by Tutorials"
  }
]

The backend part is ready. Now, it’s time to work on the frontend!

Before starting the tutorial, install Node.js if you don’t already have it on your machine. This is because React apps need Node.js to run the development server, pack the build version and manage dependencies.

Installing Node.js and npm

To create and run your React app, you have to install Node.js and its package manager, npm. The procedure is straightforward using nvm, a tool that helps you manage your Node installation. To install nvm, open a new Terminal window and run this command:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash

After that, close and reopen the terminal, and let nvm install Node with this command:

nvm install node

This will install the latest version of Node. At this point, grab a cup of coffee and wait for the completion. It can take a while.

Once the installation has completed, make the installed Node version the default one, using the alias command:

nvm alias default v18.4.0
Note: At the time of publishing, v18.4.0 is the most recent version of Node.

Now, you are all set. It’s time to build a React app!

Creating the React App

You’ll use the create-react-app CLI to set up your React project with one command.

In a new Terminal window, type this command to create your new app:

npx create-react-app frontend

npx is a package runner: It automatically downloads and updates the package before executing it. With npx, you can run create-react-app without worrying about installing it or keeping it updated. frontend refers to the project’s name.

After you’ve created the project, use this command to navigate to the new project folder.

cd frontend

You’re now in the working directory of your new project. From here, you can add dependencies and run your app.

Adding Bootstrap Theme and React Router

React is a framework that only handles drawing the interface on the browser. Therefore, if you want to add more features to React, you have to install additional packages.

Still in the frontend folder, run the following command:

npm install react-bootstrap bootstrap react-router-dom

Using npm install, you’re telling the Node Package Manager to install the specified packages in the current working directory:

  • react-bootstrap is the React version of Bootstrap, a powerful toolkit for building responsive websites.
  • The react-bootstrap library needs bootstrap to use Bootstrap in your app.
  • react-router-dom is a library that handles the routing client-side. Using React Router, you don’t need to use any fancy hosting platform for your app.

Now, open src/index.js, and add this line right before the const root declaration:

// Allows you to use Bootstrap on every page of the app
import 'bootstrap/dist/css/bootstrap.min.css';

This will include Bootstrap on every page of your app.

It’s now time to start the development server for your first React app. Run this command from the frontend folder:

npm start

After a few seconds, your browser will open a new page showing you React’s starting page, available at the URL http://localhost:3000.

React Hello World Page

Note that you only have to run this command once. After that, every time you edit a file, React automatically updates the page on your browser.

Now, the React app is running — in development mode — and so is the Vapor app. It’s time to hook them up, but first you’ll take a look at the project structure.

Understanding React App Structure

Before you dive into the creation of the app, you should know the project’s structure:

  • The node_modules folder contains all of your app’s dependencies. Don’t touch this folder because npm automatically generates and updates it.
  • Inside the public folder, you can put any asset your app should access, like images, stylesheet files, etc.
  • The src folder is your working directory; you’ll create your components inside this folder.
  • npm uses the package-lock.json file to track all the dependencies you’ve installed. You should not edit this file.
  • To change your app’s dependencies, you edit the package.json file.

Creating the BookList Component

You can compose a UI with React by combining different components. A component is a JavaScript function — or a class — that receives an input and gives a React element as output.

You’ll start with a component to list the books present in your library.

Within the src folder, create a new file called BookList.js, and add the following code:

import { useState, useEffect } from "react";
import { Container, Row, Stack, Button, Table } from "react-bootstrap";
import { NavLink } from "react-router-dom";

// 1
function BookList() {
  // TODO: Define variables and functions needed
  return (
    <>
      {/* 2 */}
      {/* TODO: Replace with React components */}
    </>
  );
}
// 3
export default BookList;

Here’s what this code does:

  1. Defines the component called BookList.
  2. Lets you compose your interface using React components.
  3. Exports the component so other components can use it. For example, you can use this component inside the index.js file.

Your first custom component is now ready, but to use it, you’ll need to set up the routing of the React app.

Routing With React Router

If you want your app to have many pages, you must implement a Router component, and then you’ll be able to specify a path for each component.

There are many types of router:

  • HashRouter allows you to create a single-page app. The page change is instantaneous since the browser isn’t changing the page. It’s all handled by the router on the client side.
  • BrowserRouter is the standard routing method. Each page has its path. Changing a page is slower than the HashRouter, and the state management can be a little trickier.
  • React Router supports other types of Routers as well. You can find all of them inside their documentation.

In this tutorial you’ll use HashRouter since it’s easier to implement. Open index.js and replace the < App /> tag with this:

{/* 1 */}
<HashRouter>
  {/* 2 */}
  <Routes>
    {/* 3 */}
    <Route exact path="/" element={<BookList />} />
    {/* TODO: Define any additional Route */}
  </Routes>
</HashRouter>

Going through this step by step:

  1. The HashRouter tag allows you to create a single-page web app with client-side routing.
  2. The Routes tag component will contain a list of Routes, which define the paths available in the app.
  3. This defines the root path /, which will soon show the BookList component.

Don’t forget to import the components at the beginning of the index.js file:

// Allows you to use ReactRoute components inside this file
import { HashRouter, Routes, Route } from "react-router-dom";
import BookList from "./BookList";
Note: It’s necessary to import every component that you’ll use in React. However, some editors like VSCode automatically import the required components as you type your code.

Now, save the file, and React will update the preview with the new index. The page at http://localhost:3000 is now empty since you still haven’t put any components inside it. That’s your next step.

Creating a Responsive Page With Bootstrap

Bootstrap uses a grid system to align content on the webpage. Thanks to containers, rows and columns, you can create responsive web apps with a few lines of code without worrying too much about the layout.

To start creating the user interface using Bootstrap, open BookList.js and replace {/* TODO: Replace with React components */} with the following:

{/* 1 */}
<Container>
  {/* 2 */}
  <Row>
    {/* 3 */}
    <Stack direction="horizontal" className="mt-3 mb-3">
      <h2>Book List</h2>
      {/* TODO: Add Button to navigate to the "New Book" page */}
    </Stack>
    {/* TODO: Add the Table component */}
  </Row>
</Container>

Here’s what each step does:

  1. You use the Container tag to center the content of your component horizontally.
  2. Since Bootstrap uses a grid system, you use Row to represent a row, which will be an entry that displays a book.
  3. The Stack tag creates a flexible container, which will have a horizontal direction and additional attributes such as mt-3 for the top margin and mb-3 for the bottom margin.

Now the page at http://localhost:3000 will look like this.

The Book list page with its title

At this point, the only thing missing is the table that displays the list of books in the library.

Creating a Table to Display Books

Before implementing the table, you need to define a variable to hold the data that React will fetch from the Vapor app.

Since the variable you’ll define will be used to draw the interface, React needs to know when the value of the variable changes. So you have to use the useState() hook to define new variables that can change the components’ layout.

In BookList.js, replace // TODO: Define variables and functions needed with:

// Define all the variables needed for the book list
const [booksData, setBooksData] = useState(null);
// TODO: Add function to load books

useState() needs an initial value and it returns the current state and a function.

You’ve created a state variable called booksData with the starting value of null. You can change the value of booksData by calling setBooksData(), passing the new value as a parameter.

Still in BookList.js, replace {/* TODO: Add the Table component */} with the following code:

{/* Bootstrap Table */}
<Table bordered hover>
  {/* Table Header */}
  <thead>
    <tr>
      <th>Book Name</th>
      <th>Author</th>
    </tr>
  </thead>
  {/* Table Body */}
  <tbody>
    {/* 1 */}
    {booksData &&
      booksData.map(({ id, title, author }) => (
        <>
          {/* 2 */}
          <tr key={id}>
            {/* 3 */}
            <td>{title}</td>
            <td>{author}</td>
          </tr>
        </>
      ))}
  </tbody>
</Table>

This will construct the table that shows the list of books. Here’s what’s happening:

  1. Check whether booksData is null. Then, render the data as a table row.
  2. Set the key of the row. React will use it to calculate diffs and optimize the performance of redrawing the UI.
  3. Show the book’s title and author as a table cell.

Now, the web page of the React app looks like this.

Book List page with title

What’s missing? Actual books! :] Your next step is fetching them from the Vapor app.

Fetching Data From the Vapor app

In BookList.js, replace // TODO: Add function to load books with:

// Function that will load the books from the back end
function loadBooks() {
  // 1
  setBooksData(null);
  // 2
  fetch("http://localhost:8080/books/")
    // 3
    .then((response) => response.json())
    // 4
    .then((json) => setBooksData(json));
}

// 5
useEffect(() => {
  loadBooks();
}, []);

This fetches and stores the books in the booksData variable. A lot’s going on here. This code:

  1. Clears the booksData variable to remove previously loaded books.
  2. Fetches the books from the backend using the fetch() function.
  3. Converts the response from a JSON string to a JavaScript object.
  4. Saves the object in the booksData variable using setBookData(json).
  5. Calls loadBooks() via the useEffect() hook.

useEffect() accepts a function and an array of dependencies as an argument. If any dependencies’ values change, the hook triggers the function. React also executes the function one time on the first render of the component.

Here, there are no dependencies to track since you only need to execute the loadBooks() function when the component is loaded.

It’s crucial to call loadBooks() from the useEffect() hook, because loadBooks() fetches data and causes the content on the view to change.

Check the page at http://localhost:3000 again, and you’ll see the list of books displayed.

Book list page with its title and table

Congrats on your first achievement! Now, what about adding new books to the list? For that, you’ll need a new component and a new page.

Creating a Page to Add New Books

You’ll start with a new component that allows users to input the book’s details and send them to the Vapor backend.

In src, create a new file called NewBook.js, and add the following code:

import { useState } from "react";
import { Button, Container, Form, Row, Stack } from "react-bootstrap";
import { NavLink, useNavigate } from "react-router-dom";

function NewBook() {
  // 1
  const [title, setTitle] = useState("");
  const [author, setAuthor] = useState("");

  // TODO: Define variables that are needed

  // 2
  const onSubmit = (e) => {
    // TODO: Implement this function
  };

  return (
    <>
      {/* Bootstrap container */}
      <Container>
        <Row>
          {/* 3 */}
	  <Stack direction="horizontal" className="mt-3 mb-3">
            <h2>Add a new book</h2>
            <Button className="ms-auto" as={NavLink} to="/">Show all books</Button>
          </Stack>
          {/* 4 */}
          <Form onSubmit={onSubmit}>
	    {/* TODO: Add components to accept user input */}
	  </Form>
        </Row>
      </Container>
    </>
  );
}
export default NewBook;

In this new component:

  1. The title and author variables hold the value of the form input elements.
  2. onSubmit() handles the Form data and sends it to the back end.
  3. A Stack tag defines the name of the page and a Button tag navigates to the Book list page.
  4. The Form component accepts the onSubmit() function. When the user hits the Submit button, the browser calls this function.

You now have the new component, but you still can’t access it. As you did with the BookList component, you have to add a new route and specify a path.

Open src/index.js and replace {/* TODO: Define any additional Route */} with the following:

{/* Define the /newbook path that will show the NewBook component */}
<Route path="/newbook" element={<NewBook />} />

And don’t forget to import NewBook at the beginning of the index.js file:

import NewBook from "./NewBook";

Now, visit http://localhost:3000/#/newbook in your browser to verify the page exists.

New book page with its title

It would be great to have a way to access this new page without knowing the path. You can use a Button to take the user to the new page.

Open BookList.js and replace {/* TODO: Add Button to navigate to the "New Book" page */} with this snippet:

{/* Button used to navigate to the "/newbook" page */}
<Button className="ms-auto" as={NavLink} to="/newbook">Add new book</Button>

By adding this button at the top of the Book List page, you’ll be able to access the new page easily.

Book list page with its add new book button

You might also notice that the new page loads instantaneously. This is because HashRouter handles all the routing on the client side.

Creating a Form

You’ll ask the user for the information about the new book using the Form. Bootstrap has beautiful forms that just need a bit more code.

In NewBook.js, replace {/* TODO: Add components to accept user input */} with the following:

{/* 1 */}
<Form.Group className="mb-3">
  {/* 2 */}
  <Form.Label>Book</Form.Label>
  {/* 3 */}
  <Form.Control
    type="text"
    required
    placeholder="Name of the book"
    value={title}
    onChange={(e) => setTitle(e.target.value)}
  />
</Form.Group>

<Form.Group className="mb-3">
  <Form.Label>Author</Form.Label>
  <Form.Control
    type="text"
    required
    placeholder="Name of the author"
    value={author}
    onChange={(e) => setAuthor(e.target.value)}
  />
</Form.Group>

{/* 4 */}
<Button variant="primary" type="submit">
  Add
</Button>

Here’s the structure of this Form:

  1. A Form.Group is just a container for a single input value. With className="mb-3", you specify that this component has a margin-bottom.
  2. The Form.Label component shows a title on top of the input label.
  3. The Form.Control is the text field used to input the book’s title. When the input’s value changes, the app updates the title variable thanks to onChange() that calls setTitle(), passing the value of the label.
  4. To submit the form, you’ll use the Bootstrap Button, but you have to specify that it’s a submit button. You alter the appearance of the button by changing the value of variant.

Now, visit http://localhost:3000/#/newbook, and you’ll see a form like this.

New Book page with its fields

It’s time to save the data that users will enter in the form.

Handling Form Data

Notice that hitting the Add button doesn’t add the book to the list because you still haven’t told React what to do with the form data.

Before completing the implementation of onSubmit(), you need to declare a new variable. Still in src/NewBook.js, replace // TODO: Define variables that are needed with:

const navigate = useNavigate();

By using navigate, you can redirect the user to another page by calling navigate("path"). React Router provides this function, and you can use it with every Router you choose.

Now, add this code inside onSubmit() to handle the form submit:

// 1
e.preventDefault();
// 2
fetch("http://localhost:8080/books/", {
  method: "POST",
  // 3
  body: JSON.stringify({ title, author }),
  headers: { "Content-Type": "application/json" },
})
// 4
.then(() => {
  // Navigate to the book list (index) using React Router
  navigate("/");
});

It should be familiar, but take a look anyway:

  1. When the user submits the form, e.preventDefault() prevents the browser from redirecting the user to another page.
  2. Make a POST request to the API with the new book’s data.
  3. Encode the new book’s data as a JSON object and place it in the request’s body.
  4. With then(), you specify what happens after the request is sent to the server.

Now, visit http://localhost:3000/#/newbook, and add a new book to the list using the form you just created.

After the book is created, you’re redirected to the list page, which includes also the new entry.

Book list page with a new book added

Congrats! You’re almost done. It’s time to package your React app and ship it to customers.

Building the React App for Production

Now that your app is complete, you’re ready to deploy the production version.

You shouldn’t deploy your development build since it can be slow. When you build your app for production, React removes all the debugging tools and minimizes the source files. This way, your app loads faster.

Quit the running npm start command and run this command from the frontend folder:

npm run build

This will run the build script, which generates a build folder containing all the necessary assets to run your app. Then, you can host it on a web server or with Vapor.

Serving the App With Vapor

Why should you pay for a hosting service while using Vapor for your backend? It’s very straightforward to host your React app on your Vapor server.

React handles the navigation with React Router. You only need to serve the index.html file and all the other assets. React Router reads the path from the browser and shows the appropriate page.

Copy the contents of the build folder inside the Public folder of the Vapor app.

You’ll have to add a small piece of code to your Vapor server before you can test your app.
Open backend-vapor/Sources/App/routes.swift and add this function at the bottom of the file:

/* Function that returns the content of the `index.html` file from the Public folder */
func serveIndex(_ req: Request, _ app: Application) -> Response {
  return req.fileio.streamFile(at: "\(app.directory.publicDirectory)/index.html")
}

Thanks to the serveIndex() function, you can return the content of the index.html file present in the Public folder. You need this function to serve the index.html file for the / path.

Inside the routes(_ app: Application) function of the routes.swift file, add these lines:

// 1
app.get { req in
  return serveIndex(req, app)
}

// 2
app.get("*") { req in
  return serveIndex(req, app)
}

Here’s what it does:

  1. Serves the index.html file when Vapor receives a GET request for the / path.
  2. This piece of code is only needed if you’re using the BrowserRouter in your React app. With this route, you tell Vapor that if there’s a GET request at the base path /, and if the Public folder has no file of that name, it returns the contents of the index.html file.

If you’re running Vapor from Xcode, make sure to set the working directory of your project.

Now, run your Vapor server and navigate to http://127.0.0.1:8080 to test your app.

Final version of MyBrary running with Vapor

Where to Go From Here

You can download the complete project using the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you’ve seen the basics of a React app, but if you want to discover React’s more advanced features, you should check its documentation.

If you want to learn more about React Router, check out the documentation or take a deep dive into it.

The React Bootstrap library gives you a lot of prebuilt components. Check out the documentation if you want to learn more.

Consider looking at Next.js, a React Framework that uses React for building the UI and allows it to handle routing, data fetching and more in a more straightforward way. With Next.js, you can also render the page server-side or create an API endpoint!

To learn more about the backend code in this tutorial, check out Server-Side Swift with Vapor.

Thank you for taking the time to read this tutorial! If you have any questions or comments, please join the forum discussion below.