Setting up fullstack TypeScript app
Tags: node, typescript, webpack, react
Published: 2019-02-28 21:47:29

---
title: Setting up fullstack TypeScript app
published: true
description: 
tags: #nodejs, #typescript, #webpack, #reactjs
---

As you know, `create-react-app` is a grate tool for scaffolding React.JS applications. It support TypeScript. But it only configures frontend part of app. If you need to set up backend too, you may run in to troubles. 

In this article I will describe my approach  of scaffolding fullstack TypeScript apps. 



## Basics

First of all lets init our new project folder. I'm gonna use `npm`. 

```bash
$ mkdir my-fullstack-typescript-app
$ cd my-fullstack-typescript-app
$ npm init -y
```



Now we should install `typescript` package as development dependency.

```bash
$ npm i -D typescript
```



We will have two different `tsconfig.json` files, one for backend, second for frontend.

Let's generate one from backend.

```bash
$ npx tsc --init
```

This will create  `tsconfig.json` file in our project root directory. I'm gonna to update some fields in it. 

Open `./tsconfig.json` in your favorite editor and change `compilerOptions.target` to `"es6"`.

Our source code will be in directory `./src` and compiled code in directory `./dist`. Uncomment and change options `compilerOptions.root` and `compilerOptions.outDir` to  `"./src"` and `"./dist"` respectively.

Also, I gonna uncomment option `compilerOptions.sourceMap` to allow debugging of compiled code.

Now your `./tsconfig.json` should look like this:

```JSON
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  }
}
```

*Note: I have removed all other commented fields to keep code short.* 



## Backend

Ok. Let's writing simple backend.

We need to install type definitions for node.js to tell TypeScript information about node.js standrard library.

```bash
$ npm i -D @types/node
```

Also I gonna use `express` as backend framework and `ejs` as template engine, so let's install them too.
```bash
$ npm i express
$ npm i -D @types/express
$ npm i ejs
```



Now we can start coding.



Let's create `./src` dir and then `./src/config.ts` file.

In this file I gonna store some configuration variables for our app.

By now, let's put there just single line of code:

```typescript
export const SERVER_PORT = parseInt(process.env.SERVER_PORT || "3000");
```



Ok. Now we can write our *web* module. 

I gonna place whole logic of web module in `./src/web` dir. 

Create file `./src/web/web.ts` with content of our *web* module:

```typescript
import express from "express";
import http from "http";
import path from "path";

// Express app initialization
const app = express();

// Template configuration
app.set("view engine", "ejs");
app.set("views", "public");

// Static files configuration
app.use("/assets", express.static(path.join(__dirname, "frontend")));

// Controllers
app.get("/*", (req, res) => {
    res.render("index");
});

// Start function
export const start = (port: number): Promise<void> => {
    const server = http.createServer(app);

    return new Promise<void>((resolve, reject) => {
        server.listen(port, resolve);
    });
};
```



Two things you can notice here. First — we need view directory `./public`. Second — we need static files directory `frontend`. 



Let's create `./public` dir (in root of our project) and place there file `index.ejs` with content:

```ejs
<!DOCTYPE html>
<html lang="en">
<head>
    <title>My Fullstack TypeScript App</title>

    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

</head>
<body>
	<div id="root"></div>

	<script src="/assets/vendors~main.chunk.js"></script>
	<script src="/assets/main.bundle.js"></script>
</body>
</html>
```

Here you can see, that we have two `script` tags, targeting frontend code bundles. We will use Webpack  to build frontend budles. 

Path to `frontend` is tricky one. Our frontend code will be stored in `./src/web/frontend` dir. But compiled bundle appear in `./dist/web/frontend`. We will set up frontend in a minute, but first let's finish backend. 

I like to treat with complex module like with a single, so let's create file `./src/web/index.ts` with a single line:

```typescript
export * from "./web";
```

And we have done with *web* module. 



The last thing left here is to create entry point file `./src/main.ts` with following content:

```typescript
import {SERVER_PORT} from "./config";

import * as web from "./web";

async function main() {
    await web.start(SERVER_PORT);
    console.log(`Server started at http://localhost:${SERVER_PORT}`);
}

main().catch(error => console.error(error));
```



Our backend is done 🥳. Let's compile it. 



Open `./package.json` file and add some npm scripts:

```json
"build:backend": "tsc",
"start": "./dist/main.js"
```

So your `./package.json` file should look like this:

```JSON
{
  "name": "my-fullstack-typescript-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build:backend": "tsc",
    "start": "node ./dist/main.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/express": "^4.16.1",
    "@types/node": "^11.9.6",
    "typescript": "^3.3.3333"
  },
  "dependencies": {
    "ejs": "^2.6.1",
    "express": "^4.16.4"
  }
}
```



Now you can chat that this part works:

```bash
$ npm run build:backend
$ npm start
```



But if we visit http://localhost:3000 we will see just black page.



## Frontend

By now our project structure looks like:

```
.
├── dist
│   ├── web
│   │   ├── index.js
│   │   ├── index.js.map
│   │   ├── web.js
│   │   └── web.js.map
│   ├── config.js
│   ├── config.js.map
│   ├── main.js
│   └── main.js.map
├── public
│   └── index.ejs
├── src
│   ├── web
│   │   ├── index.ts
│   │   └── web.ts
│   ├── config.ts
│   └── main.ts
├── package-lock.json
├── package.json
└── tsconfig.json
```



We are ready to create `./src/web/frontend` dir to store our frontend code.

Important thing here: we are using TypeScript compiler with configuration in `./tsconfig.json` to compile backend code.  But for frontend we will use Webpack and TypeScript configuration in file `./src/web/frontend/tsconfig.json`.

So let's create `./src/web/frontend` dir and initialize `./src/web/frontend/tsconfig.json` file.

```bash
$ mkdir ./src/web/frontend
$ cd ./src/web/frontend
$ npx tsc --init
```

We end up with a `tsconfig.json` file in `./src/web/frontend/`.

Let's open in and make some changes.

Again, set  `compilerOptions.target` to `"es6"`. 

Set `compilerOptions.module` to `"esnext"`. 

Uncomment option `compilerOptions.sourceMap` to allow debugging of frontend bundles.

Uncomment and set `compilerOptions.jsx` to `"react"`.

Your `./src/web/frontend/tsconfig.json` should look like:

```json
{
  "compilerOptions": {
    "target": "es6",
    "module": "esnext",
    "sourceMap": true,
    "jsx": "react",
    "strict": true,
    "esModuleInterop": true
  }
}

```

Note: we do not specify here `compilerOptions.rootDir` and `compilerOptions.outDir`. Files resolution will be done by Webpack.



Now we need to make backend compiler to ignore frontend files.

To do so, we need to add two options to `./tsconfig.json`:

```json
"include": ["./src"],
"exclude": ["./src/web/frontend"]
```



Your `./tsconfig.json` should look like:

```json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["./src"],
  "exclude": ["./src/web/frontend"]
}
```



Our frontend entry point will be `./src/web/frontend/main.tsx`:

```tsx
import React, {useState} from "react";
import ReactDOM from "react-dom";

import "./style.css";

const App = () => {
    const [counter, setCounter] = useState(0);

    return (
        <div className="App">
            <h1>{counter}</h1>
            <button onClick={() => setCounter(c + 1)}>Press me</button>
        </div>
    )
};

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

This is a very simple React.JS app. 



Let style it a little bit with `./src/web/frontend/style.css`:

```css
.App {
    margin: 30px auto;
    max-width: 320px;
    padding: 2em;
    border: 1px solid silver;
    border-radius: 1em;

    text-align: center;
}
```



Let's install needed packages: 

```bash
$ npm i react react-dom
$ npm i -D @types/react @types/react-dom
```



For building frontend I gonna use Webpack and `ts-loader` package.

Let's install all of needed stuff:

```bash
$ npm i -D webpack webpack-cli ts-loader style-loader css-loader source-map-loader
```



Now we need to configure Webpack. Let's create `./webpack.config.js` with following content:

```javascript
module.exports = {
    mode: "development",

    entry: {
        main: "./src/web/frontend/main.tsx",
    },

    output: {
        filename: "[name].bundle.js",
        chunkFilename: '[name].chunk.js',
        path: __dirname + "/dist/web/frontend",
        publicPath: "/assets/"
    },

    // Enable sourcemaps for debugging webpack's output.
    devtool: "source-map",

    resolve: {
        // Add '.ts' and '.tsx' as resolvable extensions.
        extensions: [".ts", ".tsx", ".js"]
    },

    module: {
        rules: [
            // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
            {
                test: /\.tsx?$/,
                loader: "ts-loader",
            },

            // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
            {enforce: "pre", test: /\.js$/, loader: "source-map-loader"},
            {
                test: /\.css$/,
                use: [{loader: "style-loader"}, {loader: "css-loader"}]
            },
        ]
    },

    optimization: {
        splitChunks: {
            chunks: "all"
        },
        usedExports: true
    },
};
```

And we have done!



Last thing left is to add npm script to `./package.json` file to build frontend:

```json
"build:frontend": "webpack"
```



Now you can test it:

```bash
$ npm run build:backend
$ npm run build:frontend
$ npm start
```



Goto [http://localhost:3000](http://localhost:3000)

![](https://i.ibb.co/xqC51zr/Screen-Shot-2019-03-01-at-01-39-03.png)



Full code can be found [here](https://github.com/atomAltera/my-fullstack-typescript-app-scaffold).

Have a nice day!