10 - Routing

In vanilla app, there were three different kinds of routing:

  • Static routing: Every request to the server was first checked for a static resource. If a static resource was found, it got served. This was achieved by adding an express static middleware before other routes.

  • Redirection: The request to / was redirected to upload.html. The request to /:fileId was redirected to download.html.

  • APIs: The requests such as /api/files, /api/files/:fileId/meta, and /api/files/:fileId were served by their respective express handlers.

Here is a visual representation of the different types of routing:

In the React world, the routing is done slightly differently. How it gets handled depends on the type of request.

  • Static routing: The static resources are handled by the webpack development server. The development server looks at the request URL to see if it can be served by a static resource. If it can be served, a static resource will be sent back. If not, the request will be proxied if a proxy is configured (explained very shortly).

  • Client routing: The routing of / to upload and /:fileId to download is handled at the client side by react-router package. We'll look into this shortly.

  • APIs: Any other requests (not matching the above two) are proxied if a proxy has been configured. They'll land at the express server. Due to this reason, some of the routes are not required by the express server for React.

Here is a visual representation of the different type of routing in the React world:

Just like express static server that took care of serving static files, the webpack development server takes care of serving static files. We don't have to do anything here. We'll focus on client & API routing.

Client routing

The client routing is provided in React through a popular package called react router.

React Router enables "client side routing". In traditional websites, the browser requests a document from a web server, downloads and evaluates CSS and JavaScript assets, and renders the HTML sent from the server. When the user clicks a link, it starts the process all over again for a new page.

Client side routing allows your app to update the URL from a link click without making another request for another document from the server. Instead, your app can immediately render some new UI and make data requests with fetch to update the page with new information.

This enables faster user experiences because the browser doesn't need to request an entirely new document or re-evaluate CSS and JavaScript assets for the next page. It also enables more dynamic user experiences with things like animation. (Credit: wiki).

The following shows the concept more clearly:

In the traditional world, the redirection comes from the server. In the React world, the redirection gets handled by React Router, which runs on the client side. The React Router is a feature rich package which has a number of options. Here, we're going to use three of them:

  • Browser router

  • Router provider

  • Router params (This is useful in passing the /:fileId from URL to the download component)

The basic routing mechanism is almost like express routing. A treatment (called element here) can be assigned to a route. The treatment (or element) can be JSX. We'll add two routing rules:

  • / will get handled by the Upload component

  • /:fileId will get handled by the Download component

We'll create a dummy Upload and Download component in the components folder. Here is the updated App.js with client routing:

App.js

import "bootstrap/dist/css/bootstrap.min.css";
import Upload from "./components/Upload";
import Download from "./components/Download";
import {
  createBrowserRouter,
  RouterProvider,
  useParams,
} from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Upload />,
  },
  {
    path: "/:fileId",
    element: <Download />,
  },
]);

function App() {
  return (
    <div>
      <RouterProvider router={router} />
    </div>
  );
}

export default App;

For now, the Upload and Download components just print a heading. This way, we'd be ensured that the routing is happening correctly.

// Upload.js

function Upload() {
  return <h1>Upload</h1>;
}

export default Upload;

// Download.js

function Download() {
  return <h1>Download</h1>;
}

export default Download;

Let's do a round of testing. We'll first open http://localhost:3000, which should take us to the Upload component. Then we'll open http://localhost:3000/file1, which should take us to the Download component.

The client side routing looks good so far. There is one important thing remaining. We have to pass the fileId parameter from the URL to the Download component. This can be done using the third feature of React Router: use params. There is small catch. The params can only be accessed inside a function, not at JSX level. To handle this case, we need to create a dummy function called DownloadHandler that'll take the fileId from router params and then send it to the Download compnent. At this point, we'll only see how to get the router param. The sending of router param to the Download component will be a topic for the next section, where we'll learn about props. For now, fileId will be logged using an alert. Again, this ensures us that the fileId gets received by the Download component.

The following is the updated App.js with / going to Upload and /:fileId going to a dummy DownloadHandler that'll hand it over to Download.

App.js

import "bootstrap/dist/css/bootstrap.min.css";
import Upload from "./components/Upload";
import Download from "./components/Download";
import {
  createBrowserRouter,
  RouterProvider,
  useParams,
} from "react-router-dom";

const router = createBrowserRouter([
  {
    path: "/",
    element: <Upload />,
  },
  {
    path: "/:fileId",
    element: <DownloadHandler />,
  },
]);

function DownloadHandler() {
  let { fileId } = useParams();
  alert(`Got file id: ${fileId}`);
  return <Download />;
}

function App() {
  return (
    <div>
      <RouterProvider router={router} />
    </div>
  );
}

export default App;

Proxying

Not all requests can be handled by the webpack server. All the requests starting with /api/ needs to go to the express server that's running in another terminal window. This can be achieved by adding a proxy configuration to the package.json. Only a configuration is required to send requests to a different backend server. If a proxy configuration is present, webpack will proxy all the requests that it can't handle.

// package.json
{
...
  "proxy": "http://localhost:3001"
}

That's all. This is enough to proxy the requests. We can test this out using a curl command, as we'll be making API calls from React a bit later. The curl request will fail with 404 Not Found, but we will be able to see that the response is coming from express (check the response header => x-powered-by: Express).

$ curl http://localhost:30
*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /api/rest HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.0.1
> Accept: */*
> 
< HTTP/1.1 404 Not Found
< x-powered-by: Express
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: *
< Access-Control-Allow-Headers: *
< content-security-policy: default-src 'none'
< x-content-type-options: nosniff
< content-type: text/html; charset=utf-8
< content-length: 147
< date: Wed, 17 May 2023 19:10:23 GMT
< connection: close
< Vary: Accept-Encoding
< 
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /api/rest</pre>
</body>
</html>

--

That's all about the static, client, and API routing in React. In the next section, we'll learn about props which are useful in passing data to other components.

Last updated