Frontline: Running Something
Tags: frontend, typescript, react
Published: 2022-11-05 21:01:25

Hello everyone! You have probably noticed by now that I am not a consistent writer. It is finally time to run and see something!

Today we will implement the most basic of all: a CRA client talking to an HTTP server to display something.

**In this article** | [Repository](https://github.com/hossomi/tamiyo) | [Changes](https://github.com/hossomi/tamiyo/compare/833dcd9...bfb83c5) | [Final commit](https://github.com/hossomi/tamiyo/tree/bfb83c50072c05c7e676b2ad8550933985966197)

## Server

The server is the simplest piece: an [Express](https://expressjs.com/) HTTP server that returns `Hello world` on GET. We start by installing Express in the server workspace:

```bash
yarn workspace @tamiyo/server add express
```

Then we write the server in `index.ts`:

```typescript
import express from 'express'

const app = express()

app.get('/api', (req, res) => {
    res.send("Hello world")
})

app.listen(8000, () => console.log("Listening at port 8000"))
```

The basics of Express is really simple and intuitive, so I probably don't need to explain the code above. To run, we can build and manually run the built script:

```bash
yarn tsc --build
yarn workspace @tamiyo/server node build/index.js
```

The first command will transpile Typescript into Javascript as we discussed in the last article. The second will run the transpiled Javascript file using Yarn's module resolution, required since we use workspaces.

We can test our server with `curl`:

```bash
$ curl http://localhost:8000/api
Hello world
```

## Client

The client is a basic [CRA](https://create-react-app.dev/) application that we will modify to get a message to display from our server. 

> :warning: First of all, we will disable Yarn's Plug'n'Play (PNP) resolution:
>
> ```bash
> yarn set config nodeLinker node
> yarn install
> ```
>
> Although there are sources on the internet saying that it should, I could not make Workspaces + PNP + CRA work properly. I decided to not spend more time on that and focus on actual development.

We will replace our current client with code generated with CRA:

```bash
rm -r client
yarn create react-app client --template typescript
```

Our old client had some important configuration discussed in the last article. In `package.json`, the name was changed and `type` was removed. Put them back: 

```yaml
{
  "name": "@tamiyo/client", # <-- Change back
  "version": "0.1.0",
  "private": true,
  "type": "module", # <-- Put back
  [...]
```

In `tsconfig.json`, we extracted most of the properties to `tsconfig.common.json`. So we can revert it, keeping only the few additional properties:

```yaml
{
  "extends": "../tsconfig.common.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "build",
    # Additional properties:
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "noEmit": true,
    "jsx": "react-jsx"
  }
}
```

With that, you should be able to run the template and run it in your browser (CRA will open itself) by running the `start` script with Yarn:

```bash
yarn workspace @tamiyo/client start
```

## Integrating

The last step for today is modifying our client to make a request to our server and display the response on screen. This request should be made by the `App` component:

```typescript
function App() {
  const [text, setText] = useState("Loading...")
  useEffect(() => {
    fetch('/api')
      .then(res => res.text())
      .then(setText)
  }, [])
  
  return (
    <div className="App">
      // [...]
      <p>
        {text}
      </p>
      // [...]
    </div>
  )
}
```

This should be easy if you already know React hooks. Here is a quick explanation anyway:

1. We use [`useState`](https://beta.reactjs.org/apis/react/useState) to define a state variable `text` with initial value `Loading...`. `setText` should be used to update its value.
2. We use [`useEffect`](https://beta.reactjs.org/apis/react/useEffect) with an empty array in the second argument to run an effect only once when the component is mounted.
3. This effect makes the request using `fetch`, parses the response as text using and store it in the `text` state variable, triggering a component re-render since the state changed.
4. The actual component renders `text` on screen, which will now be `Hello world`.

However, if you start the server and open the client, you will see this:

![Client without proxying displays an HTML instead of the server message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/si4uspxp99l3xwmdxooq.png)

This happens because the client is actually making the request to the CRA development server that is serving the client pages, and not our server! In production these two servers would be the same, but we are not there yet.

CRA provides a solution for this in development: [proxying](https://create-react-app.dev/docs/proxying-api-requests-in-development/). We can easily enable this in `client/package.json`:

```yaml
{
  "name": "@tamiyo/client",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "proxy": "http://localhost:8000", # <-- Proxy to here
  # [...]
```

With this, the CRA development server will proxy unknown requests (i.e. that are not requesting a client page) to our server that is running on port 8000. Restarting the client and trying again, you should finally see this:

![Client with proxying displays the server message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tvjz4hjd3zhghsv7fzfq.png)

## The shared library

Remember we have a `shared` workspace, where we will place common code? Now is a good time to link it to both client and server. For starters, let's just define two variables in `shared/index.ts`:

```typescript
export const GREETER = 'Hossomi'
export const GREETING = 'Hello world!'
```

`GREETER` will be used by the client and `GREETING` will be returned by the server. Since they are all Yarn workspaces and Yarn handles them as packages, we can install the shared library as any other package:

```bash
$ yarn workspace @tamiyo/server add @tamiyo/shared
$ yarn workspace @tamiyo/client add @tamiyo/shared
```

A small change in `shared/package.json` is also necessary so that Typescript can correctly find sources inside the shared library module:

```yaml
{
  "name": "@tamiyo/shared",
  "version": "1.0.0",
  "packageManager": "yarn@3.2.0",
  "main": "build/index.js" # <-- Add this
}
```

We can now change our server's `index.ts` like this:

```typescript
import { GREETING } from '@tamiyo/shared'

// [...]

app.get('/api', (req, res) => {
    res.send(GREETING)
})
```

And the client `App.tsx` like this:

```typescript
import { GREETER } from '@tamiyo/shared'

// [...]

<p>
  {GREETER}: {text}
</p>
```

After rebuilding and restarting our applications, we can verify that both client and server are using the variables from the shared library:

![Client and server uses variables from the shared library](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eihl9yxibki9unoluvyr.png)

## Quality of life

Today, we had to run some quite verbose commands to start our client and server. To make our lives easier, we can put them in Yarn scripts in the parent `package.json`:

```yaml
{
  "name": "tamiyo",
  "version": "1.0.0",
  # [...]
  "scripts": {
    "build": "yarn tsc --build --verbose",
    "clean": "yarn tsc --build --clean",
    "start:server": "yarn workspace @tamiyo/server start",
    "start:client": "yarn workspace @tamiyo/client start"
  },
  # [...]
```

We also need to define a `start` script in `server/package.json`:

```yaml
{
  "name": "@tamiyo/server",
  # [...]
  "scripts": {
    "start": "yarn node build/index.js"
  },
  # [...]
```

We can now run `yarn build` to transpile Typescript, `yarn start:server` to start the server and `yarn start:client` to start the client, much shorter commands! CRA even has hot reload in development mode, so changing client code while it is running will automatically update in the browser. I will investigate how to do the same for the server.

## Next step

We have a basic client-server application running, but we are using plain HTTP. In the next step, I will get started on GraphQL. It is a new world to me, so I am not sure an entire article will be needed. If it turns out simpler than I expect, I can also explore MTG JSON data sets.