Object isn't an instance of Koa on the "require" side - javascript

This is a weird bug so I must be missing something obvious, but here it is.
I'm trying to set up a Koa server to serve several Koa apps depending on the vhost name. The entry point is server.js :
const _ = require("lodash");
const compose = require("koa-compose");
const Koa = require("koa");
const server = module.exports = new Koa();
const app1 = require("./apps/app1");
const app2 = require("./apps/app2");
console.log(app1 instanceof Koa); // false (!)
console.log(app1); // `{ subdomainOffset: 2, proxy: false, env: 'development' }`
const vhostApps = [
{ vhost: "localhost", app: composer(app1) }, // composer fails because app1 is not a Koa instance
{ vhost: "app1.mydomain.com", app: composer(app1) },
{ vhost: "app2.mydomain.com", app: composer(app2) }
];
server.use(async function(ctx, next) {
const app = _(vhostApps).find({ vhost: ctx.hostname }).app;
return await app ? app.apply(this, [ctx, next]) : next();
});
if (!module.parent) server.listen(process.env.PORT || 80);
function composer(app) {
const middleware = app instanceof Koa ? app.middleware : app;
return compose(middleware);
}
Then, there's ./apps/app1/index.js, the entry point of one app:
const Koa = require("koa");
const serve = require("koa-static");
const views = require("koa-views");
const router = require("./routes");
const app = new Koa();
app.use(serve(__dirname + "/assets"));
app.use(views(__dirname + "/views", { map: { html: "lodash" } }));
app.use(router.routes());
console.log(app instanceof Koa); // true (OK)
console.log(app); // `{ subdomainOffset: 2, proxy: false, env: 'development' }`
module.exports = app;
In this module, app is an instance of Koa (thus it has a middleware property, of type array).
But seen from server.js, the value imported from app1 is not the expected Koa instance, even though the logged values of app and app1 are the same ({ subdomainOffset: 2, proxy: false, env: 'development' }
).
What am I doing wrong?

Finally I think found what the problem was, and, as it happens, the essential piece of info was missing in my question.
The ./apps/app1 folder has its own node_modules, with its own copy of Koa. Therefore, server.js and apps/app1/index.js each have their own, different, Koa.
So I suppose that in the line: const middleware = app instanceof Koa ? app.middleware : app;, app instanceof Koa will always return false for that reason.
One solution is simply to remove Koa from the app's node_modules, so Koa is inherited from the outer folder. (At first sight, it has some drawbacks for me because I would like to the apps to be standalone).
But I think I'll just skip the instanceof Koa test and have const middleware = app.middleware; instead (I borrowed the original line from https://github.com/koajs/examples/blob/master/vhost/app.js#L14).

Related

Dynamic pathRewrite with createProxyMiddleware and create react app

I have the following in my Create React App as per https://create-react-app.dev/docs/proxying-api-requests-in-development/
src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:5000',
changeOrigin: true,
})
);
};
This works fine and sends all requests to my nodejs app running on port 5000. However I wish to intercept the request somehow and rewrite the path into a url query string format.
I have json-server running on the nodejs server which needs the requests to be formatted differtently, using this type of format /api/cheeses?cheeseno=12
For example
'/api/cheese/12' => `/api/cheeses?cheeseno=12`
I have come across pathRewrite and router on this page https://www.npmjs.com/package/http-proxy-middleware but I have no idea how to map them over.
Later on as I get mor advanced, I will need to map nested path routes to url queries.
So
/location/{locationId}/aisle/{aisleid}/cheeses => /api/cheeses?locationId=12&aisleid=123`
Thanks
const { createProxyMiddleware } = require('http-proxy-middleware');
const rewriteFn = function (path, req) {
return path.replace('/api/foo', '/api/bar');
};
const options = {
target: 'http://localhost:3000',
pathRewrite: rewriteFn,
};
const apiProxy = createProxyMiddleware('/api', options);
rewriteFn is the key
https://github.com/chimurai/http-proxy-middleware/blob/master/recipes/pathRewrite.md#custom-rewrite-function

Angular 9 SSR ReferenceError: stripe.elements is not defined

for my project Angular 9 I must install Server-side rendering (SSR), I followed official tutorial https://angular.io/guide/universal. At the beginning I have the problem with the window is not define. So I decided to install SSR with domino and I followed this tutorial enter link description here , but I have a problem when the program build my project : elements is not define. (const elements = stripe.elements() Cannot read property elements of undefined).
Below my server.ts code
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
import * as core from 'express-serve-static-core';
const domino = require('domino');
const fs = require('fs');
const path = require('path');
// Use the browser index.html as template for the mock window
const template = fs
.readFileSync(path.join(join(process.cwd(), 'dist/captn-boat-angular/browser'), 'index.html'))
.toString();
// Shim for the global window and document objects.
const window = domino.createWindow(template);
global['window'] = window;
global['document'] = window.document;
global ['navigator']=window.navigator;
global ['screen']=window.screen;
global['Event'] = null;
global['window'] = window;
global['document'] = window.document;
global['branch'] = null;
global['object'] = window.object;
global['localStorage'] = window.localStorage;
global['navigator'] = window.navigator ;
global['elements']=window.elements;
global['elements']=null;
global['Event'] = null;
global['KeyboardEvent'] = null;
global['stripe']=window.stripe;
window.screen = { deviceXDPI: 0, logicalXDPI: 0 };
global['MouseEvent'] = window.MouseEvent;
declare interface Window {
Stripe: any; // Or you can define a type for that in this file as well
stripe:null;
elements:null;
}
import { AppServerModule } from './src/main.server';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): core.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/captn-boat-angular/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run() {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
And then the error : elements of undefined
Thank you for your answer.
Do you use stripe in every place on your application ? I'd use it in the payment section of the related module and would connect it to lazy loading. Thus you could use one of the importer of stripe ?
like here :ngx-stripe installation

Engine not found for the ".js" file extension

I want to use koa-views with Koa and Koa-Router with Next.js. In previous projects, I had no issues with express but in this project, I have to use Koa. Using its router, I want to render a page: /some/page/:id. Following the same Nextjs way:
router.get('/some/page/:id', async (ctx, next) => {
const actualPage = '/some/page/id' // id.js (not actual name 😝)
await ctx.render(actualPage, {/* could pass object */})
});
That would work if I was using express. With Koa:
const Koa = require('koa');
const views = require('koa-views');
// const render = require('koa-views-render'); <-- I what's this?
[..] // Making things short here
const server = new Koa();
const router = new Router();
// My issue, I'm seeing tutorials using other engines: .ejs etc
// I'm not using any, I only have .js files
server.use(views(__dirname + "/pages", { extension: 'js' }));
Using the same router.get... function as above, I get:
Error: Engine not found for the ".js" file extension
When I go to /some/page/123, I'd expect it to render the file /pages/some/page/id.js. How?
It turns out I do not need any extra modules to achieve this 🙀
Create a function called, ie, routes then pass app and router as a param
const routes = (router, app) => {
router.get('/some/page/:id', async (ctx) => {
const { id } = ctx.params
const actualPage = '/some/page/id'
// Render the page
await app.render(ctx.req, ctx.res, actualPage, {foo: 'Bar'})
}
}
module.exports = routes
Inside your server.js file:
// const routes = require('./routes);
// const app = next({ dev }); // import other modules for this section
// app.prepare().then(() => {
// const router = new Router();
// [..]
// routes(router, app)
// })
The commented out section is a slim down version to make a point in where things should be.

How do I mock my config file for testing?

I have a Koa app I just started and I need to test something that grabs data from a config file.
I need to test with specific data, but I'm not sure how to modify what data the test receives from the config file.
Example:
app.js
var router = require('koa-router');
var config = require('./config.js');
var db = require('./db.js');
var auth = require('./auth');
var app = require('koa')();
router.get('/', function *() {
if(auth(this.req, config.credentials.secret)) { // Authenticates request based on a hash created using a shared secret
this.body = "Request has been authenticated";
}
});
app.use(router.routes());
app = module.exports = http.createServer(app.callback());
app.listen(3000);
appSpec.js
var request = require('supertest');
var app = require('../app.js');
describe('app', function() {
it('should authenticate all requests against config shared secret', function() {
var secret = 'some_secret';
var validHash = /* hash created from test secret and query */;
request(app)
.get('/')
.query({query: 'some_query'})
.query({hash: validHash})
.expect(403, done);
});
});
This spec will fail because the app will use the secret from the config file(empty string) instead of my test secret.
Alright, I played around with some different ways to handle this.
The best option I found, for my particular use case, was proxyquire. It's an npm package that lets you override dependencies in any file that you require in your test suites.
So if I am testing this module:
./controllers/myController.js
var config = require('../config.js');
module.exports = function() {
// Do some stuff
};
I would do something like this:
./test/controllers/myControllerSpec.js
var proxyquire = require('proxyquire');
var config = {
credentials: {
secret: 'my_secret'
}
// other fake config stuff
};
var myController = proxyquire('../../controllers/myController.js', {'../config.js', config});
describe('myController', function() {
// TESTS
});
and this instance of myController will use my test config.
This won't work for end to end testing, unless the only place you import your config is the main app file.
I use node-config for my config files and configuration loading based on machine or env variable.
You can specify your config in a variety of formats (.json, .js, yaml, etc.) Using the default settings, you need to create a config folder in your app root and a default.<format> with your default config.
To override that for testing you can create a test.<format> file in your config directory. When you set your NODE_ENV=test, then node-config will see load your default config file and then it will load your test config file and if there are any conflicts for the values, your test config file will override the values in your default file.
Here are the full docs for setting up Configuration Files in node-config
Below is an example using node-config with a .js config file format.
./config/default.js
module.exports = {
credentials: {
secret: ''
}
}
./config/test.js
module.exports = {
credentials: {
secret: 'abcdef123456'
}
}
app.js
var router = require('koa-router');
var config = require('config');
var db = require('./db.js');
var auth = require('./auth');
var app = require('koa')();
var credentials = config.get('credentials');
router.get('/', function *() {
if(auth(this.req, credentials.secret)) { // Authenticates request based on a hash created using a shared secret
this.body = "Request has been authenticated";
}
});
app.use(router.routes());
app = module.exports = http.createServer(app.callback());
app.listen(3000);
Jest has nice support for this case. In your test file, add
jest.mock('../config.js', () => ({
credentials: {
secret: 'my_secret'
}
// other fake config stuff }));

Mocha API Testing: getting 'TypeError: app.address is not a function'

My Issue
I've coded a very simple CRUD API and I've started recently coding also some tests using chai and chai-http but I'm having an issue when running my tests with $ mocha.
When I run the tests I get the following error on the shell:
TypeError: app.address is not a function
My Code
Here is a sample of one of my tests (/tests/server-test.js):
var chai = require('chai');
var mongoose = require('mongoose');
var chaiHttp = require('chai-http');
var server = require('../server/app'); // my express app
var should = chai.should();
var testUtils = require('./test-utils');
chai.use(chaiHttp);
describe('API Tests', function() {
before(function() {
mongoose.createConnection('mongodb://localhost/bot-test', myOptionsObj);
});
beforeEach(function(done) {
// I do stuff like populating db
});
afterEach(function(done) {
// I do stuff like deleting populated db
});
after(function() {
mongoose.connection.close();
});
describe('Boxes', function() {
it.only('should list ALL boxes on /boxes GET', function(done) {
chai.request(server)
.get('/api/boxes')
.end(function(err, res){
res.should.have.status(200);
done();
});
});
// the rest of the tests would continue here...
});
});
And my express app files (/server/app.js):
var mongoose = require('mongoose');
var express = require('express');
var api = require('./routes/api.js');
var app = express();
mongoose.connect('mongodb://localhost/db-dev', myOptionsObj);
// application configuration
require('./config/express')(app);
// routing set up
app.use('/api', api);
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('App listening at http://%s:%s', host, port);
});
and (/server/routes/api.js):
var express = require('express');
var boxController = require('../modules/box/controller');
var thingController = require('../modules/thing/controller');
var router = express.Router();
// API routing
router.get('/boxes', boxController.getAll);
// etc.
module.exports = router;
Extra notes
I've tried logging out the server variable in the /tests/server-test.js file before running the tests:
...
var server = require('../server/app'); // my express app
...
console.log('server: ', server);
...
and I the result of that is an empty object: server: {}.
You don't export anything in your app module. Try adding this to your app.js file:
module.exports = server
It's important to export the http.Server object returned by app.listen(3000) instead of just the function app, otherwise you will get TypeError: app.address is not a function.
Example:
index.js
const koa = require('koa');
const app = new koa();
module.exports = app.listen(3000);
index.spec.js
const request = require('supertest');
const app = require('./index.js');
describe('User Registration', () => {
const agent = request.agent(app);
it('should ...', () => {
This may also help, and satisfies #dman point of changing application code to fit a test.
make your request to the localhost and port as needed
chai.request('http://localhost:5000')
instead of
chai.request(server)
this fixed the same error message I had using Koa JS (v2) and ava js.
The answers above correctly address the issue: supertest wants an http.Server to work on. However, calling app.listen() to get a server will also start a listening server, this is bad practice and unnecessary.
You can get around by this by using http.createServer():
import * as http from 'http';
import * as supertest from 'supertest';
import * as test from 'tape';
import * as Koa from 'koa';
const app = new Koa();
# add some routes here
const apptest = supertest(http.createServer(app.callback()));
test('GET /healthcheck', (t) => {
apptest.get('/healthcheck')
.expect(200)
.expect(res => {
t.equal(res.text, 'Ok');
})
.end(t.end.bind(t));
});
Just in case, if someone uses Hapijs the issue still occurs, because it does not use Express.js, thus address() function does not exist.
TypeError: app.address is not a function
at serverAddress (node_modules/chai-http/lib/request.js:282:18)
The workaround to make it work
// this makes the server to start up
let server = require('../../server')
// pass this instead of server to avoid error
const API = 'http://localhost:3000'
describe('/GET token ', () => {
it('JWT token', (done) => {
chai.request(API)
.get('/api/token?....')
.end((err, res) => {
res.should.have.status(200)
res.body.should.be.a('object')
res.body.should.have.property('token')
done()
})
})
})
Export app at the end of the main API file like index.js.
module.exports = app;
We had the same issue when we run mocha using ts-node in our node + typescript serverless project.
Our tsconfig.json had "sourceMap": true . So generated, .js and .js.map files cause some funny transpiling issues (similar to this). When we run mocha runner using ts-node. So, I will set to sourceMap flag to false and deleted all .js and .js.map file in our src directory. Then the issue is gone.
If you have already generated files in your src folder, commands below would be really helpful.
find src -name ".js.map" -exec rm {} \;
find src -name ".js" -exec rm {} \;
I am using Jest and Supertest, but was receiving the same error. It was because my server takes time to setup (it is async to setup db, read config, etc). I needed to use Jest's beforeAll helper to allow the async setup to run. I also needed to refactor my server to separate listening, and instead use #Whyhankee's suggestion to create the test's server.
index.js
export async function createServer() {
//setup db, server,config, middleware
return express();
}
async function startServer(){
let app = await createServer();
await app.listen({ port: 4000 });
console.log("Server has started!");
}
if(process.env.NODE_ENV ==="dev") startServer();
test.ts
import {createServer as createMyAppServer} from '#index';
import { test, expect, beforeAll } from '#jest/globals'
const supertest = require("supertest");
import * as http from 'http';
let request :any;
beforeAll(async ()=>{
request = supertest(http.createServer(await createMyAppServer()));
})
test("fetch users", async (done: any) => {
request
.post("/graphql")
.send({
query: "{ getQueryFromGqlServer (id:1) { id} }",
})
.set("Accept", "application/json")
.expect("Content-Type", /json/)
.expect(200)
.end(function (err: any, res: any) {
if (err) return done(err);
expect(res.body).toBeInstanceOf(Object);
let serverErrors = JSON.parse(res.text)['errors'];
expect(serverErrors.length).toEqual(0);
expect(res.body.data.id).toEqual(1);
done();
});
});
Edit:
I also had errors when using data.foreach(async()=>..., should have use for(let x of... in my tests

Categories

Resources