Creating a web application using Yarn workspace, TypeScript, esbuild, React, and Express (part 2)

Posted on March 12, 2021

This article will guide you through setting up a basic web application using Yarn's workspace, TypeScript, esbuild, Express, and React. At the end of this tutorial you will have a fully buildable and deployable web application.

  1. Setting up the project (part 1)
  2. Adding code (part 2)
    1. Common
    2. App
    3. Server
    4. Summary
  3. Building the app (part 3)
  4. Going further (advanced)

Want to see the full code? Check out the repository on GitHub at halftheopposite/tutorial-app.


The second part will focus on adding the code to our common, app, and server packages.

Common

We will start with common as this package will be used by both app and server. It's goal is to provide shared logic and variables.

Files

For the purpose of this tutorial the common package will be quite simple. First, start by adding a new folder:

  • A src/ folder, containing our package's code.

Once this folder has been created, add the following file into it:

src/index.ts

export const APP_TITLE = 'my-app';

Now that we have some code to export, we want to tell TypeScript where to look for it when importing it from other packages. To do so, we will need to update the package.json file:

package.json

{
  "name": "@my-app/common",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true,
  "main": "./src/index.ts" // Add this line to provide TS with an entrypoint to this package.
}

We have now completed the common package!

Structure reminder:

common/
├─ src/
│  ├─ index.ts
├─ package.json

App

Dependencies

The app package will need the following dependencies:

From the project's root, run:

  • yarn app add react react-dom
  • yarn app add -D @types/react @types/react-dom (to add the typings for TypeScript)

package.json

{
  "name": "@my-app/app",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "@my-app/common": "^0.1.0", // Notice that we've added this import manually
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  },
  "devDependencies": {
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.2"
  }
}

Files

To create our React application we will need to add two new folders:

  • A public/ folder, that will hold the base HTML page and our assets.
  • A src/ folder, containing our application's code.

Once these two folders have been created, we can start by adding the HTML file that will be our application's host.

public/index.html

<!DOCTYPE html>
<html>
  <head>
    <title>my-app</title>
    <meta name="description" content="Welcome on my application!" />
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <!-- This div is where we will inject our React application -->
    <div id="root"></div>
    <!-- This is the path to the script containing our application -->
    <script src="script.js"></script>
  </body>
</html>

Now that we have a page to render, we can implement our very basic, but functional, React application, by adding the two files below.

src/index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import { App } from './App';

ReactDOM.render(<App />, document.getElementById('root'));

This code hooks into the root div from our HTML file and injects the React component tree into it.

src/App.tsx

import { APP_TITLE } from '@flipcards/common';
import * as React from 'react';

export function App(): React.ReactElement {
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <h1>Welcome on {APP_TITLE}!</h1>
      <p>
        This is the main page of our application where you can confirm that it
        is dynamic by clicking the button below.
      </p>

      <p>Current count: {count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
    </div>
  );
}

This simple App component will render our app's title and a dynamic counter. It will be the entrypoint to our React tree. Feel free to add any code that you would like.

And that's it! We've completed our very basic React application. It doesn't do much for now, but we can always come back to it later and add more features.

Structure reminder:

app/
├─ public/
│  ├─ index.html
├─ src/
│  ├─ App.tsx
│  ├─ index.tsx
├─ package.json

Server

Dependencies

The server package will need the following dependencies:

From the project's root, run:

  • yarn server add cors express
  • yarn server add -D @types/cors @types/express (to add the typings for TypeScript)

package.json

{
  "name": "@my-app/server",
  "version": "0.1.0",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "@my-app/common": "^0.1.0", // Notice that we've added this import manually
    "cors": "^2.8.5",
    "express": "^4.17.1"
  },
  "devDependencies": {
    "@types/cors": "^2.8.10",
    "@types/express": "^4.17.11"
  }
}

Files

Our React app is now ready, and the last part that we need is a server to serve it. Start by creating the following folder to it:

  • A src/ folder, containing our server's code.

Next, add the main file for our server:

src/index.ts

import { APP_TITLE } from '@flipcards/common';
import cors from 'cors';
import express from 'express';
import { join } from 'path';

const PORT = 3000;

const app = express();
app.use(cors());

// Serve static resources from the "public" folder (ex: when there are images to display)
app.use(express.static(join(__dirname, '../../app/public')));

// Serve the HTML page
app.get('*', (req: any, res: any) => {
  res.sendFile(join(__dirname, '../../app/public', 'index.html'));
});

app.listen(PORT, () => {
  console.log(`${APP_TITLE}'s server listening at http://localhost:${PORT}`);
});

This is a very basic Express application, but if we don't have anything else beside a Single-Page Application to serve, then that's pretty much all we need.

Structure reminder:

server/
├─ src/
│  ├─ index.ts
├─ package.json

Summary

All modules are now ready and our small app has all the code it needs to run. However, since we are using TypeScript, which is not pure JavaScript, we need to a way to convert all our files into something that is parsable and executable by Node.js (for our server), or the browser (for our React application). This step is called transpiling. The next post will explain how to build and serve our application using a bundler.

Like this article?

Have some questions? Email me at contact[at]halftheopposite.dev.