DEV Community

Dirk Hoekstra
Dirk Hoekstra

Posted on

How I structure my Express + Typescript + React applications

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
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode

The react app will be created in the frontend folder.

Let’s test if the installation went correctly.

cd frontend
yarn run start
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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}!`));
    }

}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"
  },
Enter fullscreen mode Exit fullscreen mode

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

mkdir build 
cd frontend
yarn run build
Enter fullscreen mode Exit fullscreen mode

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"
  }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

build/
├─app/
├─frontend/
├─index.js
Enter fullscreen mode Exit fullscreen mode

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");
    });
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 🚀

Oldest comments (10)

Collapse
 
mateiadrielrafael profile image
Matei Adriel

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

Collapse
 
dance2die profile image
Sung M. Kim • Edited

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.

Collapse
 
dance2die profile image
Sung M. Kim

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.

Collapse
 
dirk94 profile image
Dirk Hoekstra

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!

Collapse
 
dance2die profile image
Sung M. Kim

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.

Collapse
 
trasherdk profile image
TrasherDK

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

Collapse
 
leejjon_net profile image
Leejjon • Edited

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.

Collapse
 
princebillygk profile image
Prince Billy Graham Karmoker • Edited

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.

Collapse
 
princebillygk profile image
Prince Billy Graham Karmoker

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

Collapse
 
princebillygk profile image
Prince Billy Graham Karmoker • Edited

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

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

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

yarn add ts-node -D
Enter fullscreen mode Exit fullscreen mode