Is there a better way to structure the express entry point? - node.js

I'm relatively new programmer. What I'm currently trying to do is to create a good code base, from which I can then develop the API I need. The requirements for that are, that I want to use NodeJS with Express and TypeScript. So I've created a new express bootstrapped project with the express-generator and started to convert the code into a TypeScript conform structure.
My question starts here:
I moved and renamed the "root/bin/www" file into "src/bin/index.ts". My tslint is set to "recommended" and therefore the "no-shadowed-variable" is true. So I got a "no-shadowed-variable" error in my index.ts, because the variable port is defined twice. Once in the outer function, as well as in the "onListening" function. So I started to separate the "onListening" and the "onError" function into a new module called "expressHandlers.ts", and the "normalizePort" function into "normalizePort.ts".
You can see the full project structure on: https://github.com/owme/server-template/tree/master/src/bin
It's currently working right now, but would you please let me know if you think this approach to rename and modularize the bin/www is reasonable?
Btw. I had to rewrite parts of the "onListening" function, because addr can be of type AddressInfo | string | null. And null is not a valid entry of the variable.
So my index.ts now exports the two variables { port, server } to be available for normalizePort. What is left, in the index.ts is the following:
index.ts:
#!/usr/bin/env node
'use strict';
/**
* Module dependencies.
* #private
*/
import { createServer } from 'http';
import app, { set } from '../app';
import { onError, onListening } from './expressHandlers';
import { normalizePort } from './normalizePort';
/**
* Get port from environment and store in Express.
*/
const port = normalizePort(process.env.PORT || '3000');
set('port', port);
/**
* Create HTTP server.
*/
const server = createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Module exports.
* #public
*/
export {
port,
server
};
normalizePort.ts:
'use strict';
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val: number|string): number|string|boolean {
const port: number = (typeof val === 'string') ? parseInt(val, 10) : val;
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Module exports.
* #public
*/
export {
normalizePort
};
expressHandler:
'use strict';
/**
* Module dependencies.
* #private
*/
import debug from 'debug';
debug('server:server');
import {
port,
server
} from './index';
/**
* Event listener for HTTP server "error" event.
*/
function onError(error: NodeJS.ErrnoException) {
if (error.syscall !== 'listen') {
throw error;
}
const bind = (typeof port === 'string') ? `Pipe ${port}` : `Port ${port}`;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
debug(`${bind} requires elevated privileges`);
process.exit(1);
break;
case 'EADDRINUSE':
debug(`${bind} is already in use`);
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
const addr = server.address();
let bind = null;
if (addr !== null) {
if (typeof addr === 'string') {
bind = `pipe ${addr}`;
} else {
bind = `port ${addr.port}`;
}
} else {
throw new Error('The Host-Address is null');
}
debug(`Listening on ${bind}`);
}
/**
* Module exports.
* #public
*/
export {
onError,
onListening
};

Related

When to load .env variables in NodeJS app?

I am coding a simple NodeJS Express REST API, using TypeScript. I have some environment variables that I load with dotenv.
I access my .env variables at two different stages in my code: index.ts, which is my start file, and in a MyControllerClass.ts file. To access these variables, the code is process.env.MY_ENV_VAR. To load them for the application, the code is dotenv.config().
As my index.ts file seems to be the root of my program (I configure my app in it), I use dotenv.config() to load my .env file for the rest of the program. However, in my MyControllerClass.ts file, in the constructor, if I do console.log(process.env.MY_ENV_VAR), I get "undefined". I could workaround this by adding a dotenv.config() in my constructor (it works) but it's nonsense to me to have it here.
How do I use dotenv.config() once and for all in my program, in a readable manner (like in an appropriate .ts file)? and more generally: what is a NodeJS Express loading cycle?
Here is a sample of the file structure of my code
src
├── index.ts
├── Authentication
│ └── authentication.router.ts
│ └── authentication.controller.ts
Here is the code of index.js
/**
* Required External Modules
*/
import * as dotenv from "dotenv";
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { authenticationRouter } from "./authentication/authentication.router"
dotenv.config();
/**
* App Variables
*/
if(!process.env.PORT) {
process.exit(1);
}
const PORT: number = parseInt(process.env.PORT as string, 10);
const app = express();
/**
* App Configuration
*/
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(authenticationRouter);
app.use("api/authenticate/", authenticationRouter);
/**
* Server Activation
*/
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
Here is the code of authentication.router.ts
import express, { Request, Response } from "express";
import { AuthenticatorController } from "./authentication.controller";
export const authenticationRouter = express.Router();
const authenticatorController = AuthenticatorController.getInstance();
authenticationRouter.post("/api/authenticate", async (req: Request, res: Response) => {
try {
if (await authenticatorController.authenticate(req.body.login, req.body.password)) {
res.send({"status": "ok"})
} else
res.send({"status": "Error"})
} catch (e) {
console.debug(e)
res.send({"status": "500"});
}
});
Here is the code of authentication.controller.ts
import { ClientSecretCredential } from "#azure/identity";
import { SecretClient } from "#azure/keyvault-secrets";
import { Authenticator } from "./api/Authenticator";
import * as dotenv from "dotenv";
dotenv.config();
export class AuthenticatorController implements Authenticator {
private static singleInstance: AuthenticatorController | null = null;
private azureSecretCredential= new ClientSecretCredential(
process.env.AZURE_TENANT_ID as string,
process.env.AZURE_CLIENT_ID as string,
process.env.AZURE_CLIENT_SECRET as string);
private azureSecretClient = new SecretClient(
process.env.KEY_VAULT_URL as string,
this.azureSecretCredential);
private constructor () {}
public static getInstance(): AuthenticatorController {
if (this.singleInstance === null) {
this.singleInstance = new AuthenticatorController();
}
return this.singleInstance;
}
public async authenticate(login: string, password: string): Promise<Boolean> {
let isAuthenticated = false;
try {
const secret = await this.azureSecretClient.getSecret(login)
if (secret.name === login) {
if (secret.value === password) {
isAuthenticated = true;
}
}
} catch (e) {
console.debug(e);
}
return isAuthenticated;
}
}
You only call dotenv.config() once:
As early as possible in your application, require and configure
dotenv.
require('dotenv').config()
Therefore index.ts seems to be correct, process.env should then hold your parsed values. Maybe you can use something like this to make sure, data is parsed correctly:
const result = dotenv.config();
if (result.error) {
throw result.error;
}
console.log(result.parsed);
Edit:
You can try the following. I changed your exports a bit, because there is no need for a singleton within your controller.
authentication.router.ts:
// Imports (no dotenv; no dotenv.config())
// [...]
// Import controller
import { authenticatorController } from "./authentication.controller";
export const authenticationRouter = express.Router();
// Adding routes
// [...]
authentication.controller.ts:
// Imports (no dotenv; no dotenv.config())
// [...]
class AuthenticatorController implements Authenticator {
// [...]
}
export const authenticatorController = new AuthenticatorController();
index.ts:
// Imports (dotenv)
// [...]
const { error, parsed } = dotenv.config();
if (error) {
throw error;
}
console.log(parsed);
// [...]
app.use("api/authenticate/", authenticationRouter);
// [...]

TypeError [ERR_UNKNOWN_FILE_EXTENSION]:

This is the command i'm trying to run ./bitgo-express --port 3080 --env test --bind localhost and I'm receiving following error:
(node:367854) ExperimentalWarning: The ESM module loader is experimental.
internal/process/esm_loader.js:90
internalBinding('errors').triggerUncaughtException(
^
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension "" for /home/root/example.com/BitGoJS/modules/express/bin/bitgo-express
at Loader.defaultGetFormat [as _getFormat] (internal/modules/esm/get_format.js:65:15)
at Loader.getFormat (internal/modules/esm/loader.js:116:42)
at Loader.getModuleJob (internal/modules/esm/loader.js:247:31)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async Loader.import (internal/modules/esm/loader.js:181:17)
at async Object.loadESM (internal/process/esm_loader.js:84:5) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
bitgo-express:
#!/usr/bin/env node
const { init } = require('../src/expressApp.ts');
if (require.main === module) {
init()
.catch(err => {
console.log(`Fatal error: ${err.message}`);
console.log(err.stack);
});
}
expressApp.ts:
/**
* #prettier
*/
import * as express from 'express';
import * as httpProxy from 'http-proxy';
import * as url from 'url';
import * as Bluebird from 'bluebird';
import * as path from 'path';
import * as _ from 'lodash';
import * as debugLib from 'debug';
import * as https from 'https';
import * as http from 'http';
import * as morgan from 'morgan';
import * as fs from 'fs';
import { Request as StaticRequest } from 'express-serve-static-core';
import { Config, config } from './config';
const debug = debugLib('bitgo:express');
// eslint-disable-next-line #typescript-eslint/camelcase
import { SSL_OP_NO_TLSv1 } from 'constants';
import { IpcError, NodeEnvironmentError, TlsConfigurationError } from './errors';
import { Environments } from 'bitgo';
import { setupRoutes } from './clientRoutes';
const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
const BITGOEXPRESS_USER_AGENT = `BitGoExpress/${pjson.version} BitGoJS/${version}`;
/**
* Set up the logging middleware provided by morgan
*
* #param app
* #param config
*/
function setupLogging(app: express.Application, config: Config): void {
// Set up morgan for logging, with optional logging into a file
let middleware;
if (config.logFile) {
// create a write stream (in append mode)
const accessLogPath = path.resolve(config.logFile);
const accessLogStream = fs.createWriteStream(accessLogPath, { flags: 'a' });
console.log('Log location: ' + accessLogPath);
// setup the logger
middleware = morgan('combined', { stream: accessLogStream });
} else {
middleware = morgan('combined');
}
app.use(middleware);
morgan.token('remote-user', function(req) {
return req.isProxy ? 'proxy' : 'local_express';
});
}
/**
* If we're running in a custom env, set the appropriate environment URI and network properties
*
* #param config
*/
function configureEnvironment(config: Config): void {
const { customRootUri, customBitcoinNetwork } = config;
if (customRootUri) {
Environments['custom'].uri = customRootUri;
}
if (customBitcoinNetwork) {
Environments['custom'].network = customBitcoinNetwork;
}
}
/**
* Create and configure the proxy middleware and add it to the app middleware stack
*
* #param app bitgo-express Express app
* #param config
*/
function configureProxy(app: express.Application, config: Config): void {
const { env, timeout } = config;
// Mount the proxy middleware
const options = {
timeout: timeout,
proxyTimeout: timeout,
secure: true,
};
if (Environments[env].network === 'testnet') {
// Need to do this to make supertest agent pass (set rejectUnauthorized to false)
options.secure = false;
}
const proxy = httpProxy.createProxyServer(options);
const sendError = (res: http.ServerResponse, status: number, json: object) => {
res.writeHead(status, {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(json));
};
proxy.on('proxyReq', function(proxyReq, req) {
// Need to rewrite the host, otherwise cross-site protection kicks in
const parsedUri = url.parse(Environments[env].uri).hostname;
if (parsedUri) {
proxyReq.setHeader('host', parsedUri);
}
const userAgent = req.headers['user-agent']
? BITGOEXPRESS_USER_AGENT + ' ' + req.headers['user-agent']
: BITGOEXPRESS_USER_AGENT;
proxyReq.setHeader('User-Agent', userAgent);
});
proxy.on('error', (err, _, res) => {
debug('Proxy server error: ', err);
sendError(res, 500, {
error: 'BitGo Express encountered an error while attempting to proxy your request to BitGo. Please try again.',
});
});
proxy.on('econnreset', (err, _, res) => {
debug('Proxy server connection reset error: ', err);
sendError(res, 500, {
error:
'BitGo Express encountered a connection reset error while attempting to proxy your request to BitGo. Please try again.',
});
});
app.use(function(req: StaticRequest, res: express.Response) {
if (req.url && (/^\/api\/v[12]\/.*$/.test(req.url) || /^\/oauth\/token.*$/.test(req.url))) {
req.isProxy = true;
proxy.web(req, res, { target: Environments[env].uri, changeOrigin: true });
return;
}
// user tried to access a url which is not an api route, do not proxy
res.status(404).send('bitgo-express can only proxy BitGo API requests');
});
}
/**
* Create an HTTP server configured for accepting HTTPS connections
*
* #param config application configuration
* #param app
* #return {Server}
*/
async function createHttpsServer(
app: express.Application,
config: Config & { keyPath: string; crtPath: string }
): Promise<https.Server> {
const { keyPath, crtPath } = config;
const privateKeyPromise = fs.promises.readFile(keyPath, 'utf8');
const certificatePromise = fs.promises.readFile(crtPath, 'utf8');
const [key, cert] = await Promise.all([privateKeyPromise, certificatePromise]);
// eslint-disable-next-line #typescript-eslint/camelcase
return https.createServer({ secureOptions: SSL_OP_NO_TLSv1, key, cert }, app);
}
/**
* Create an HTTP server configured for accepting plain old HTTP connections
*
* #param app
* #return {Server}
*/
function createHttpServer(app: express.Application): http.Server {
return http.createServer(app);
}
/**
* Create a startup function which will be run upon server initialization
*
* #param config
* #param baseUri
* #return {Function}
*/
export function startup(config: Config, baseUri: string): () => void {
return function() {
const { env, ipc, customRootUri, customBitcoinNetwork } = config;
console.log('BitGo-Express running');
console.log(`Environment: ${env}`);
if (ipc) {
console.log(`IPC path: ${ipc}`);
} else {
console.log(`Base URI: ${baseUri}`);
}
if (customRootUri) {
console.log(`Custom root URI: ${customRootUri}`);
}
if (customBitcoinNetwork) {
console.log(`Custom bitcoin network: ${customBitcoinNetwork}`);
}
};
}
/**
* helper function to determine whether we should run the server over TLS or not
*/
function isTLS(config: Config): config is Config & { keyPath: string; crtPath: string } {
const { keyPath, crtPath } = config;
return Boolean(keyPath && crtPath);
}
/**
* Create either a HTTP or HTTPS server
* #param config
* #param app
* #return {Server}
*/
export async function createServer(config: Config, app: express.Application) {
return isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);
}
/**
* Create the base URI where the BitGoExpress server will be available once started
* #return {string}
*/
export function createBaseUri(config: Config): string {
const { bind, port } = config;
const tls = isTLS(config);
const isStandardPort = (port === 80 && !tls) || (port === 443 && tls);
return `http${tls ? 's' : ''}://${bind}${!isStandardPort ? ':' + port : ''}`;
}
/**
* Check environment and other preconditions to ensure bitgo-express can start safely
* #param config
*/
function checkPreconditions(config: Config) {
const { env, disableEnvCheck, bind, ipc, disableSSL, keyPath, crtPath, customRootUri, customBitcoinNetwork } = config;
// warn or throw if the NODE_ENV is not production when BITGO_ENV is production - this can leak system info from express
if (env === 'prod' && process.env.NODE_ENV !== 'production') {
if (!disableEnvCheck) {
throw new NodeEnvironmentError(
'NODE_ENV should be set to production when running against prod environment. Use --disableenvcheck if you really want to run in a non-production node configuration.'
);
} else {
console.warn(
`warning: unsafe NODE_ENV '${process.env.NODE_ENV}'. NODE_ENV must be set to 'production' when running against BitGo production environment.`
);
}
}
const needsTLS = !ipc && env === 'prod' && bind !== 'localhost' && !disableSSL;
// make sure keyPath and crtPath are set when running over TLS
if (needsTLS && !(keyPath && crtPath)) {
throw new TlsConfigurationError('Must enable TLS when running against prod and listening on external interfaces!');
}
if (Boolean(keyPath) !== Boolean(crtPath)) {
throw new TlsConfigurationError('Must provide both keypath and crtpath when running in TLS mode!');
}
if ((customRootUri || customBitcoinNetwork) && env !== 'custom') {
console.warn(`customRootUri or customBitcoinNetwork is set, but env is '${env}'. Setting env to 'custom'.`);
config.env = 'custom';
}
}
export function app(cfg: Config): express.Application {
debug('app is initializing');
const app = express();
setupLogging(app, cfg);
const { debugNamespace, disableProxy } = cfg;
// enable specified debug namespaces
if (_.isArray(debugNamespace)) {
_.forEach(debugNamespace, ns => debugLib.enable(ns));
}
checkPreconditions(cfg);
// Be more robust about accepting URLs with double slashes
app.use(function(req, res, next) {
req.url = req.url.replace(/\/\//g, '/');
next();
});
// Decorate the client routes
setupRoutes(app, cfg);
configureEnvironment(cfg);
if (!disableProxy) {
configureProxy(app, cfg);
}
return app;
}
/**
* Prepare to listen on an IPC (unix domain) socket instead of a normal TCP port.
* #param ipcSocketFilePath path to file where IPC socket should be created
*/
export async function prepareIpc(ipcSocketFilePath: string) {
if (process.platform === 'win32') {
throw new IpcError(`IPC option is not supported on platform ${process.platform}`);
}
try {
const stat = fs.statSync(ipcSocketFilePath);
if (!stat.isSocket()) {
throw new IpcError('IPC socket is not actually a socket');
}
// ipc socket does exist and is indeed a socket. However, the socket cannot already exist prior
// to being bound since it will be created by express internally when binding. If there's a stale
// socket from the last run, clean it up before attempting to bind to it again. Arguably, it would
// be better to do this before exiting, but that gets a bit more complicated when all exit paths
// need to clean up the socket file correctly.
fs.unlinkSync(ipcSocketFilePath);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
export async function init(): Bluebird<void> {
const cfg = config();
const expressApp = app(cfg);
const server = await createServer(cfg, expressApp);
const { port, bind, ipc } = cfg;
const baseUri = createBaseUri(cfg);
if (ipc) {
await prepareIpc(ipc);
server.listen(ipc, startup(cfg, baseUri));
} else {
server.listen(port, bind, startup(cfg, baseUri));
}
server.timeout = 300 * 1000; // 5 minutes
}
Node.js version: 12.19.1
NPM version: 6.14.8
How can I fix this issue? I saw this SyntaxError: Cannot use import statment without a module, TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Catch 22 which was similar to mine. But that answer didn't help me as then I'll have to edit the whole file and even I did I'm stuck in 38 line. If someone can translate the whole code into node friendly require, Please translate it. Much appreciated!
You can run your script like this:
npx ts-node ./path/to/your-script.ts
You may want to build your scripts with typescript compiler and run the build result as ordinary JS script.
But for that you need to setup some bundler like for example Webpack and you need to configure plugins for handling Typescript compilation for example you can use Bable.

Translate expressApp.ts to a node friendly require

I want to change translate "import" to a node friendly require in following code.
Example: import * as express from 'express'; to const express = require('express');
I'm stucked at import { Request as StaticRequest } from 'express-serve-static-core'; line. Can someone help me, please
I want to translate the whole code to node friendly code
/**
* #prettier
*/
import * as express from 'express';
import * as httpProxy from 'http-proxy';
import * as url from 'url';
import * as Bluebird from 'bluebird';
import * as path from 'path';
import * as _ from 'lodash';
import * as debugLib from 'debug';
import * as https from 'https';
import * as http from 'http';
import * as morgan from 'morgan';
import * as fs from 'fs';
import { Request as StaticRequest } from 'express-serve-static-core';
import { Config, config } from './config';
const debug = debugLib('bitgo:express');
// eslint-disable-next-line #typescript-eslint/camelcase
import { SSL_OP_NO_TLSv1 } from 'constants';
import { IpcError, NodeEnvironmentError, TlsConfigurationError } from './errors';
import { Environments } from 'bitgo';
import { setupRoutes } from './clientRoutes';
const { version } = require('bitgo/package.json');
const pjson = require('../package.json');
const BITGOEXPRESS_USER_AGENT = `BitGoExpress/${pjson.version} BitGoJS/${version}`;
/**
* Set up the logging middleware provided by morgan
*
* #param app
* #param config
*/
function setupLogging(app: express.Application, config: Config): void {
// Set up morgan for logging, with optional logging into a file
let middleware;
if (config.logFile) {
// create a write stream (in append mode)
const accessLogPath = path.resolve(config.logFile);
const accessLogStream = fs.createWriteStream(accessLogPath, { flags: 'a' });
console.log('Log location: ' + accessLogPath);
// setup the logger
middleware = morgan('combined', { stream: accessLogStream });
} else {
middleware = morgan('combined');
}
app.use(middleware);
morgan.token('remote-user', function(req) {
return req.isProxy ? 'proxy' : 'local_express';
});
}
/**
* If we're running in a custom env, set the appropriate environment URI and network properties
*
* #param config
*/
function configureEnvironment(config: Config): void {
const { customRootUri, customBitcoinNetwork } = config;
if (customRootUri) {
Environments['custom'].uri = customRootUri;
}
if (customBitcoinNetwork) {
Environments['custom'].network = customBitcoinNetwork;
}
}
/**
* Create and configure the proxy middleware and add it to the app middleware stack
*
* #param app bitgo-express Express app
* #param config
*/
function configureProxy(app: express.Application, config: Config): void {
const { env, timeout } = config;
// Mount the proxy middleware
const options = {
timeout: timeout,
proxyTimeout: timeout,
secure: true,
};
if (Environments[env].network === 'testnet') {
// Need to do this to make supertest agent pass (set rejectUnauthorized to false)
options.secure = false;
}
const proxy = httpProxy.createProxyServer(options);
const sendError = (res: http.ServerResponse, status: number, json: object) => {
res.writeHead(status, {
'Content-Type': 'application/json',
});
res.end(JSON.stringify(json));
};
proxy.on('proxyReq', function(proxyReq, req) {
// Need to rewrite the host, otherwise cross-site protection kicks in
const parsedUri = url.parse(Environments[env].uri).hostname;
if (parsedUri) {
proxyReq.setHeader('host', parsedUri);
}
const userAgent = req.headers['user-agent']
? BITGOEXPRESS_USER_AGENT + ' ' + req.headers['user-agent']
: BITGOEXPRESS_USER_AGENT;
proxyReq.setHeader('User-Agent', userAgent);
});
proxy.on('error', (err, _, res) => {
debug('Proxy server error: ', err);
sendError(res, 500, {
error: 'BitGo Express encountered an error while attempting to proxy your request to BitGo. Please try again.',
});
});
proxy.on('econnreset', (err, _, res) => {
debug('Proxy server connection reset error: ', err);
sendError(res, 500, {
error:
'BitGo Express encountered a connection reset error while attempting to proxy your request to BitGo. Please try again.',
});
});
app.use(function(req: StaticRequest, res: express.Response) {
if (req.url && (/^\/api\/v[12]\/.*$/.test(req.url) || /^\/oauth\/token.*$/.test(req.url))) {
req.isProxy = true;
proxy.web(req, res, { target: Environments[env].uri, changeOrigin: true });
return;
}
// user tried to access a url which is not an api route, do not proxy
res.status(404).send('bitgo-express can only proxy BitGo API requests');
});
}
/**
* Create an HTTP server configured for accepting HTTPS connections
*
* #param config application configuration
* #param app
* #return {Server}
*/
async function createHttpsServer(
app: express.Application,
config: Config & { keyPath: string; crtPath: string }
): Promise<https.Server> {
const { keyPath, crtPath } = config;
const privateKeyPromise = fs.promises.readFile(keyPath, 'utf8');
const certificatePromise = fs.promises.readFile(crtPath, 'utf8');
const [key, cert] = await Promise.all([privateKeyPromise, certificatePromise]);
// eslint-disable-next-line #typescript-eslint/camelcase
return https.createServer({ secureOptions: SSL_OP_NO_TLSv1, key, cert }, app);
}
/**
* Create an HTTP server configured for accepting plain old HTTP connections
*
* #param app
* #return {Server}
*/
function createHttpServer(app: express.Application): http.Server {
return http.createServer(app);
}
/**
* Create a startup function which will be run upon server initialization
*
* #param config
* #param baseUri
* #return {Function}
*/
export function startup(config: Config, baseUri: string): () => void {
return function() {
const { env, ipc, customRootUri, customBitcoinNetwork } = config;
console.log('BitGo-Express running');
console.log(`Environment: ${env}`);
if (ipc) {
console.log(`IPC path: ${ipc}`);
} else {
console.log(`Base URI: ${baseUri}`);
}
if (customRootUri) {
console.log(`Custom root URI: ${customRootUri}`);
}
if (customBitcoinNetwork) {
console.log(`Custom bitcoin network: ${customBitcoinNetwork}`);
}
};
}
/**
* helper function to determine whether we should run the server over TLS or not
*/
function isTLS(config: Config): config is Config & { keyPath: string; crtPath: string } {
const { keyPath, crtPath } = config;
return Boolean(keyPath && crtPath);
}
/**
* Create either a HTTP or HTTPS server
* #param config
* #param app
* #return {Server}
*/
export async function createServer(config: Config, app: express.Application) {
return isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);
}
/**
* Create the base URI where the BitGoExpress server will be available once started
* #return {string}
*/
export function createBaseUri(config: Config): string {
const { bind, port } = config;
const tls = isTLS(config);
const isStandardPort = (port === 80 && !tls) || (port === 443 && tls);
return `http${tls ? 's' : ''}://${bind}${!isStandardPort ? ':' + port : ''}`;
}
/**
* Check environment and other preconditions to ensure bitgo-express can start safely
* #param config
*/
function checkPreconditions(config: Config) {
const { env, disableEnvCheck, bind, ipc, disableSSL, keyPath, crtPath, customRootUri, customBitcoinNetwork } = config;
// warn or throw if the NODE_ENV is not production when BITGO_ENV is production - this can leak system info from express
if (env === 'prod' && process.env.NODE_ENV !== 'production') {
if (!disableEnvCheck) {
throw new NodeEnvironmentError(
'NODE_ENV should be set to production when running against prod environment. Use --disableenvcheck if you really want to run in a non-production node configuration.'
);
} else {
console.warn(
`warning: unsafe NODE_ENV '${process.env.NODE_ENV}'. NODE_ENV must be set to 'production' when running against BitGo production environment.`
);
}
}
const needsTLS = !ipc && env === 'prod' && bind !== 'localhost' && !disableSSL;
// make sure keyPath and crtPath are set when running over TLS
if (needsTLS && !(keyPath && crtPath)) {
throw new TlsConfigurationError('Must enable TLS when running against prod and listening on external interfaces!');
}
if (Boolean(keyPath) !== Boolean(crtPath)) {
throw new TlsConfigurationError('Must provide both keypath and crtpath when running in TLS mode!');
}
if ((customRootUri || customBitcoinNetwork) && env !== 'custom') {
console.warn(`customRootUri or customBitcoinNetwork is set, but env is '${env}'. Setting env to 'custom'.`);
config.env = 'custom';
}
}
export function app(cfg: Config): express.Application {
debug('app is initializing');
const app = express();
setupLogging(app, cfg);
const { debugNamespace, disableProxy } = cfg;
// enable specified debug namespaces
if (_.isArray(debugNamespace)) {
_.forEach(debugNamespace, ns => debugLib.enable(ns));
}
checkPreconditions(cfg);
// Be more robust about accepting URLs with double slashes
app.use(function(req, res, next) {
req.url = req.url.replace(/\/\//g, '/');
next();
});
// Decorate the client routes
setupRoutes(app, cfg);
configureEnvironment(cfg);
if (!disableProxy) {
configureProxy(app, cfg);
}
return app;
}
/**
* Prepare to listen on an IPC (unix domain) socket instead of a normal TCP port.
* #param ipcSocketFilePath path to file where IPC socket should be created
*/
export async function prepareIpc(ipcSocketFilePath: string) {
if (process.platform === 'win32') {
throw new IpcError(`IPC option is not supported on platform ${process.platform}`);
}
try {
const stat = fs.statSync(ipcSocketFilePath);
if (!stat.isSocket()) {
throw new IpcError('IPC socket is not actually a socket');
}
// ipc socket does exist and is indeed a socket. However, the socket cannot already exist prior
// to being bound since it will be created by express internally when binding. If there's a stale
// socket from the last run, clean it up before attempting to bind to it again. Arguably, it would
// be better to do this before exiting, but that gets a bit more complicated when all exit paths
// need to clean up the socket file correctly.
fs.unlinkSync(ipcSocketFilePath);
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
}
}
export async function init(): Bluebird<void> {
const cfg = config();
const expressApp = app(cfg);
const server = await createServer(cfg, expressApp);
const { port, bind, ipc } = cfg;
const baseUri = createBaseUri(cfg);
if (ipc) {
await prepareIpc(ipc);
server.listen(ipc, startup(cfg, baseUri));
} else {
server.listen(port, bind, startup(cfg, baseUri));
}
server.timeout = 300 * 1000; // 5 minutes
}
You can use Destructuring and Rename variable syntax of ES6.
require('express-serve-static-core') returns a object, just do it as a normal object.
const { Request: StaticRequest } = require('express-serve-static-core');

Injecting App as a parameter vs Import App from server.js in loopBack framework

I'm using loopback3 and I'm thinking about optimising the codebase now, what is the point of injecting the application object as a parameter to getUser method from afterRemote hook https://loopback.io/doc/en/lb2/Remote-hooks.html, if I can access loopback instance by requiring it directly inside userService.ts.
I feel that I'm missing something important here, I tried to summarise below
A. For me importing is better since I will have less code and, there
will be no need to inject the app each time.
B. Injected and Imported
objects are equal, _.isEqual(app, App)
C. I've checked the
performance with process.hrtime() and got same results.
app/models/activity.ts
import {UserService} from 'app/service/userService';
import {Attach} from 'app/models/remote/activityRemote';
export = function (Activity) {
Activity.afterRemote('find', function (ctx, result, next) {
UserService.getUser(Activity.app, Activity.username)
.then(() => next())
.catch(next);
});
/**
* attach remote
*/
Attach(Activity);
};
userService.ts
import {Server} from 'app/server/interface/server';
import * as App from 'app/server/server';
import * as _ from 'lodash';
/**
* #class UserService
*/
export class UserService {
/**
* get user's public profile
* #param {Server} app loopback app
* #param {string} username
* #returns {Promise<User>}
*/
public static getUser(app: Server, username: string): Promise<User> {
return App.models.user.findOne(filter) // Equal and
return app.models.user.findOne(filter) // does the same
.then((user: User) => {
if (!user) {
return Promise.reject(ServerError.get('User not found', 404));
}
return Promise.resolve(user);
});
}
}
server.ts
import {Server} from 'app/server/interface/server';
import * as loopback from 'loopback';
import * as boot from 'loopback-boot';
let App: Server = loopback();
module.exports = App;
export = App;
App.start = () => {
return App.listen(() => {
const baseUrl = App.get('url').replace(/\/$/, '');
App.emit('started');
console.log('Web server listening at: %s', baseUrl);
if (App.get('loopback-component-explorer')) {
console.log(
'Browse your REST API at %s%s',
baseUrl,
App.get('loopback-component-explorer').mountPath
);
}
});
};
boot(App, __dirname, (err: Error) => {
if (err) {
throw err;
}
if (require.main === module) {
App.start();
}
});
Unless you have multiple loopback applications in the same process going around, then you have no reason to pass the application as a parameter. Just import the app as it's more readable and cleaner.
Also you don't need to use Promise.resolve when you already have a promise:
return app.models.user.findOne(filter) // does the same
.then((user: User) => {
if (!user) {
throw ServerError.get('User not found', 404);
}
return user;
});
This will have the same effect.

RequireJS import documentation

Im using in WebStorm editor. My project is using RequireJS with AMD. There is an example of code:
dep.js
define([], function () {
var exports = {
helloWorld: function() {
console.log("Hello world");
}
};
return exports;
});
primary.js
define(['dep'], function (dep) {
var exports = {
sayHello: function() {
dep.helloWorld();
}
};
return exports;
});
How to document properly exports (this mainly described in other answers) and (important!) imports of such AMD modules, so WebStorm can have proper type hints on imported deps (like a "dep" variable in this example).
According to AMD howto, it should be smth like
/**
* #module dep
*/
define([], function() {
/**
* #constructor
* #alias module:dep
*/
var exports = {
helloWorld: function() {
console.log("Hello world");
}
};
return exports;
});

Resources