# Building a local https server with Expressjs

As part of the tools which works with [Single SPA Framework](https://single-spa.js.org/) we have one called [import-map-overrides](https://github.com/single-spa/import-map-overrides) which allow us override the import maps values. By overriding an specific "*micro front end"* with a local environment version we are able to work with the deployed environment and improve our experience as a full stack engineer.

However, working with the deployed environment *"is not all roses"*, specially if it is over HTTPS... and one of those problems is "[Same Origin Policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)"

## The Same-origin policy

"*The* ***same-origin policy*** *is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin.*"

For this URL [https://maxmartinez.dev/about-me](https://maxmartinez.dev/about-me) we can have the following origin comparisons examples:

* https://maxmartinez.dev/newsletter: Same origin, only the path differs.
    
* http://maxmartinez.dev/about-me: Failure, ***different protocol***.
    

To tackle this situation we are going to setup an https server for a local development.

## Setting up the project

Firstly, we have to create our nodejs project:

```bash
npm init --yes # this will trigger automatically populated initialization with default values
```

Then, install the project's dependencies which are necessary to work with typescript and expressjs.

```bash
npm install express office-addin-dev-certs cors 
# Dev dependencies
npm install --save-dev @types/express @types/node @types/cors typescript ts-node ts-node-dev
```

*Special mention for "office-addin-devs-certs" which allows us to* manage certificates for development server using *https://localhost.*

### Project structure

Create the following structure in your local project folder:

```bash
.
├── src
│   ├── app.ts
│   └── www
│       ├── api.router.ts
│       ├── http.server.ts
│       ├── https.server.ts
│       └── server.interface.ts
└── tsconfig.json
```

### Typescript configurations

For setup typescript properly, add the following settings to the tsconfig.json file:

```typescript
{
  "compilerOptions": {
    "target": "es2017",
    "outDir": "./dist",
    "rootDir": "src",
    "moduleResolution": "node",
    "module": "commonjs",
    "declaration": true,
    "inlineSourceMap": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "types": ["node"],
    "typeRoots": ["node_modules/@types"]
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules/**"],
}
```

# The web server layer

### Server interface

We are going to define the IServer interface contract which ensure that the HTTP and HTTPS servers implement the same behavior. Add the following interface into the *server.interface.ts* file:

```typescript
export interface IServer {
  port: number;
  bootstrap: () => Promise<void>;
  listen: () => void;
}
```

Let's explain the IServer interface:

**port**: Define port number which the server use to listens the connections.

**bootstrap**: The bootstrap method allows to implement what "staffs" the server needs to do before start. For example: Load routes.

**listen**: The listen method allows to implement how the frameworks start listening the connections.

### HTTP Server

Add the HttpServer class into the *http.server.ts* file to handle the Express web server configuration over *http* protocol:

```typescript
import express from "express";
import { apiRouter } from "./api.router";
import { IServer } from "./server.interface";

export class HttpServer implements IServer {
  port: number;
  protected framework: express.Application;
  constructor(port: number) {
    this.port = port;
    this.framework = express();
  }

  async bootstrap(): Promise<void> {
    // Load Express Middlewares
    this.framework.use(this.loadMiddlewares());

    // Routers
    this.framework.use(apiRouter());
  }

  async listen(): Promise<void> {
    this.framework.listen(this.port, () => {
      console.log(`[HTTP] Server started at port: ${this.port}`);
    });
  }

  protected loadMiddlewares() {
    return [express.json()];
  }
}
```

**bootstrap**: The "bootstrap" method is setting the express json middleware which parses incoming requests with JSON payloads. And also, this method is loading the API Routes.

**listen**: The "listen" method encapsulate the logic related to how the "ExpressJs Framework" needs to be configured to listening for connections.

**loadMiddlewares**: Load the builtin express middlewares. In this case, we only have "express.json()" midleware.

### HTTPS Server

Basically the HttpsServer class extends from HttpServer class and override the listen method to add the https feature.

Add the HttpsServer class into the *https.server.ts* file to handle the Express web server configuration over ***https*** protocol:

```typescript
import https from "https";
import cors from "cors";
import { HttpServer } from "./http.server";
import devCerts = require("office-addin-dev-certs");

export class HttpsServer extends HttpServer {
  async listen() {
    const options = await devCerts.getHttpsServerOptions();
    const server = https.createServer(options, this.framework);
    server.listen(this.port, () => {
      console.log(`[HTTPS] Cors enabled: *`);
      console.log(`[HTTPS] Server started at port: ${this.port}`);
    });
  }

  protected loadMiddlewares() {
    return [...super.loadMiddlewares(), cors()];
  }
}
```

**listen:** By using the node:https module we need to provide two parameters:

* The localhost certificate options (from "office-addin-dev-certs")
    
* Express instance (this.framework) as "Request Listener"
    

**loadMiddlewares**: We override the super class method to keep the HTTP server middlewares and add the "cors" middleware only for HTTPS scope.

### API Router

To define how our web server handles client requests we have to implement the ExpressJs Router. So, add the following functions into the *api.router.ts* file:

```typescript
import {
  Router,
  Request as ExpressRequest,
  Response as ExpressResponse,
  NextFunction as ExpressNextFunction,
} from "express";

const router = Router();

export const apiRouter = () => {
  router.get(
    "/health-check",
    (req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => {
      res.status(200).json({ status: "OK" });
    }
  );

  return router;
};
```

*\*we only define the "/health-check" route. You can have whatever you need.*

# App

The app acts as the central point for managing the running process. So, add the App class into the *app.ts* file:

```typescript
import { HttpServer } from "./www/http.server";
import { HttpsServer } from "./www/https.server";
import { IServer } from "./www/server.interface";

export class App {
  constructor(private server: IServer) {}
  async bootstrap(): Promise<void> {
    // framework bootstrapping
    await this.server.bootstrap();
  }

  async run(): Promise<void> {
    this.server.listen();
  }
}

const makeServerInstance = (port: number): IServer => {
  return process.env.ENABLE_HTTPS === "true"
    ? new HttpsServer(port)
    : new HttpServer(port);
};

async function start() {
  const port = 9001;
  const server = makeServerInstance(port);
  const app = new App(server);
  await app.bootstrap();
  await app.run();
}

start();
```

**bootstrap**: The "bootstrap" method initializes the web server.

**run**: The "run" method encapsulates the logic to put the server running.

**makeServerInstance:** A "factory" function which make and return the http or https server instance.

**start**: The "start" function set the server port, injected the Application Dependencies (web server), initialize the application and run the server.

## Package json

To run our project we have to modify the scripts section of the package.json file and ad the necessary scripts to build and run the http or https server.

```json
...
"scripts": {
    "build": "tsc --build",
    "start": "ts-node-dev --respawn ./src/app.ts",
    "start:https": "ENABLE_HTTPS=true npm start"
  },
...
```

**build**: Build the typescript project and their dependencies.

**start**: Start express server over *HTTP* protocol

**start:https:** Start express server over ***HTTPS*** protocol. By passing the *"ENABLE\_HTTPS" environment variable equal true* we tell to the App that we wanted to run a HTTPS server.

# Testing

## HTTP Server

In your terminal execute the followed command to start an instance of http server:

```bash
npm run start
```

HTTP CURL request:

```bash
curl -I http://localhost:9001/health-check
#output
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 15
ETag: W/"f-v/Y1JusChTxrQUzPtNAKycooOTA"
Date: Fri, 12 Apr 2024 04:48:20 GMT
Connection: keep-alive
Keep-Alive: timeout=5
```

## HTTPS Server

To start an https server, execute in your terminal execute:

```bash
npm run start:https
```

HTTPS CURL request:

```bash
curl --verbose https://localhost:9001/health-check
#output
*   Trying [::1]:9001...
* Connected to localhost (::1) port 9001
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted http/1.1
* Server certificate:
*  subject: CN=127.0.0.1
*  start date: Apr  8 05:44:00 2024 GMT
*  expire date: May  8 05:44:00 2024 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: CN=Developer CA for Microsoft Office Add-ins; C=US; ST=WA; L=Redmond; O=Developer CA for Microsoft Office Add-ins
*  SSL certificate verify ok.
* using HTTP/1.1
> GET /health-check HTTP/1.1
> Host: localhost:9001
> User-Agent: curl/8.4.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Access-Control-Allow-Origin: *
< Content-Type: application/json; charset=utf-8
< Content-Length: 15
< ETag: W/"f-v/Y1JusChTxrQUzPtNAKycooOTA"
< Date: Fri, 12 Apr 2024 05:06:11 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< 
* Connection #0 to host localhost left intact
```

We can notice that the issuer of the certificate is "Developer CA for Microsoft Office Add-ins" and also, we got a new header related the "cors" configuration: "Access-Control-Allow-Origin: \*"

Now that you’ve read this post, you know exactly how implement an http & https server and what useful it is when you need to integrate your deployed environment with your local env.

See you in the next article.

Get the source files from my [Github](https://github.com/maxmartinezc/express-https-server) repository.
