loading...

How I structure my Express + Typescript + React applications

dirk94 profile image Dirk Hoekstra ・4 min read

In this article, I will show you how I set up and structure my Express — React projects.

Folder structure

When I set up a React Express app I always use the following folder structure.

├─app
├─build
├─frontend
  • The app directory will hold the Express backend application.

  • The build directory will hold the production build of the frontend and backend application

  • The frontend directory will hold the React frontend application.

Note that you are free to use any other folder structure that you like, this is simply my preferred way of doing things.

Creating the React app

Let’s begin with creating the React app. I’m going to use the create-react-app npm package for this.

You can run npm packages without installing them using the npx tool.

npx create-react-app frontend

The react app will be created in the frontend folder.

Let’s test if the installation went correctly.

cd frontend
yarn run start

The yarn run start command will run the React development server. Whenever you make changes to a file it will automatically recompile the react app and reload the browser! 🚀

React app

The create-react-app package will also initialize a git repository in the frontend directory. However, I want to have a single git repository in the project root directory.

To remove the git repository in the frontend directory I simply remove the .git directory.

rm -rf .git

Creating the Express app

We now have a working frontend application, now it’s time to set up the backend Typescript Express app.

I start by creating a new package in the project root directory.

Then I add the Express and Typescript dependencies and finally, I create the app directory.

yarn init
yarn add express @types/express typescript
mkdir app

Next, I create a pretty standard tsconfig.json file. This file contains the settings for compiling Typescript to Javascript.

{
  "compilerOptions": {
    "module": "commonjs",
    "baseUrl": "./",
    "outDir": "build",
    "target": "es6",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "lib": ["es6"],
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
  },
  "include": [
    "**.ts"
  ],
  "exclude": [
    "./frontend"
  ]
}

I only want to use Typescript in the backend — at least for now. That is why I exclude the frontend directory.

In the app directory, I will create a Server.ts that will contain a Server class.


import {Express, Request, Response} from "express";

export class Server {

    private app: Express;

    constructor(app: Express) {
        this.app = app;

        this.app.get("/api", (req: Request, res: Response): void => {
            res.send("You have reached the API!");
        })
    }

    public start(port: number): void {
        this.app.listen(port, () => console.log(`Server listening on port ${port}!`));
    }

}

This class will receive the Express app in the constructor and initialize the application routes.

In the real world, I would probably create another class Router.ts that will hold all the application routes, but that is out of scope for this article.

To initialize this server I create a index.ts file in the application root directory. All this does is create a new Server class and start the server.

import {Server} from "./app/Server";
import express from 'express';
const app = express();

const port = 8080;

const server = new Server(app);
server.start(port);

To start the backend server I add the following snippet to the package.json file. It will use the ts-node package to directly run Typescript code.

This way you won't have to worry about compiling the Typescript to Javascript as it is all done for you.

"scripts": {
    "start": "npx ts-node index.ts"
}

That why I can start the server running the following command.

API window

As a bonus, you can use Nodemon to automatically restart ts-node when a file changes.

Building the React app

Let’s make a production build of the React app.

I will make a change to the frontend/package.json file. Because after building the React application I want to move the build files to the /build/frontend folder.

Find the "scripts" and update the "build" line.

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build && mv ./build ../build/frontend"
  },

Let’s run the yarn run build command and see if it works! 🙌

mkdir build 
cd frontend
yarn run build

If you navigate to the /build/frontend directory you will see the production-ready React app!

Building the Express app

Let’s add a new "build" script to the package.json file.

"scripts": {
    "start": "npx ts-node index.ts",
    "build": "npx tsc"
  }

This will simply call the Typescript compiler package tsc to compile — or transpile if you prefer 💁‍♂— the Typescript to Javascript.

Run the build command to test if it works!

yarn run build

If all went correctly your build directory should look like this.

build/
├─app/
├─frontend/
├─index.js

Connecting Express and React

We can develop the backend and frontend applications and build them. However, we should also connect Express to React.

For example, if I browse to localhost:8080/ I should get to see the React application.

If I browse to localhost:8080/api I should get to see the API message.

To do this I update the constructor of the Server class.


constructor(app: Express) {
    this.app = app;

    this.app.use(express.static(path.resolve("./") + "/build/frontend"));

    this.app.get("/api", (req: Request, res: Response): void => {
        res.send("You have reached the API!");
    });

    this.app.get("*", (req: Request, res: Response): void => {
        res.sendFile(path.resolve("./") + "/build/frontend/index.html");
    });
}

First I mark the build/frontend directory as a static asset directory. This means that Express will automatically serve the files in that directory.

Next, I add a wildcard * route and send those all to the index.html file. This is the file that holds the React frontend application.

Let’s rerun the backend application.

yarn run start

When navigation to localhost:8080 I get to see the React application 🎉

React app 2

When navigating to localhost:8080/api I get to see the API message 🔥

API message

That’s it! You can find the source code here on Github 🚀

Posted on by:

dirk94 profile

Dirk Hoekstra

@dirk94

Practical programmer that likes building cool stuff

Discussion

markdown guide
 

Hey Dirk!

This looks like a good post here. Can you share this in full on DEV?

DEV generally asks that folks share their posts in full if possible and there is tooling provided (dev.to/settings/publishing-from-rss) to make it so that it's relatively easy to repost from outside blogs.

Hope you'll consider sharing the full post going forward.

 

Hi Sung, thanks for the heads-up I didn't realize posting my Medium link was a bad thing. From now on I'll post the whole articles on dev.to as well!

 

Thanks, Dirk for the updated article~ 🙂

No worries as different community has different terms of use (ToU) .

Should you want more info on ToU, check out the Terms of Use.

 

don't forget about adding this to your package.json file

scripts {
"dev": "nodemon --watch '**/*.ts' --exec 'ts-node' index.ts"
}

and to make this work you also need to install ts-node as dev-dependency

yarn add ts-node -D
 

I literally came to dev cause I didn't like medium -_-

 

DEV normally request authors to post full content, as mentioned in my other comment. For some reason, many medium posts (paywalled mostly) aren't exported correctly via RSS.

 

Great article, I'm using a similar approach to host my blindpool.com app on Google App Engine with node.js. This tutorial will help me convert it to TypeScript without having to Google every step :)

One thing though, at the point where you tell the user to add this:

this.app.use(express.static(path.resolve("./") + "/build/frontend"));

this.app.get("/api", (req: Request, res: Response): void => {
   res.send("You have reached the API!");
});

this.app.get("*", (req: Request, res: Response): void => {
   res.sendFile(path.resolve("./") + "/build/frontend/index.html");
});

Maybe also inform they have to add the imports:

import express from "express";
import * as path from "path";

I had to look them up from your github project.

 

That's work fine. But I am getting a blank page when I am trying to do the same thing with a typescript version of reacting.

 

ok then what will be the nodemon index.js alternative here?

 

I came to read the article, not to get click baited.