I am trying to unit test my router object in Express but inside the unit test file the object returns undefined
Here is a minimal version of my app
src/config/apiVersion.js
// update major versions here
const version = '/v2'
export default version
src/routes/index.js
import express from 'express'
import {
healthRouter,
healthUrl
} from './health/index.js'
const router = express.Router()
// add new routes here
const allRoutes = [
{
path: healthUrl,
route: healthRouter
}
]
// tell the router to use the routes you added
allRoutes.forEach((route) => {
router.use(route.path, route.route)
})
export default router
src/routes/health/index.js
import express from 'express'
import { healthController } from '../../controllers/health/index.js'
const healthRouter = express.Router()
const healthUrl = '/health'
healthRouter.route('/')
.get(healthController)
export {
healthRouter,
healthUrl
}
src/app.js (note I omitted most of the app.use's such as app.us(cors()) for example
// version is just the string '/v2'
import version from './config/apiVersion.js'
import router from './routes/index.js'
const app = express()
// some other app.use's here omitted like app.use(cors)
// add routes
app.use(`${version}`, router)
// custom 404 to handle non-existent paths/typos on paths
app.use((req, res) => {
res.status(404).send({ error: 'Path does not exist, check for typos. If querying /soap you also need vendor and method in the path' })
})
// custom error handler
app.use((err, req, res) => {
appLogger.error('There was an error: ' + err.stack)
res.status(500).send('Something broke!')
})
export default app
Here is my test file
import router from '../../../src/routes/index.js'
// to make sure the number of routes doesn't change without a new test added
const actualNumberRoutes = 2
describe('router', () => {
it('should return all the routes', () => {
let numberOfRoutes = 0
router.stack.forEach((layer) => {
expect(layer.name).toEqual('router')
numberOfRoutes += 1
})
expect(numberOfRoutes).toEqual(actualNumberRoutes)
})
})
And the error for this file where router is coming up as undefined
Try to provide your app to use routes after importing your respective route files like this.
import healthRouter from 'src/routes/health/index.js';
import router from '../../../src/routes/index.js';
const app=express();
app.use("your_path",router);
app.use("your_health_path",healthRouter);
Related
I use gRPC but I have a problem initializing the service in Next.js app.
Goal: Create client service only once in app and use it in getServerSideProps (app doesn't use client-side routing).
For example, we have a service generated with grpc-tools (only available on SSR) and then I just want to initialize it somewhere. At first I thought it can be realized in a custom server.js:
const { credentials } = require('#grpc/grpc-js');
const express = require("express");
const next = require("next");
const { MyserviceClient } = require('./gen/myservice_grpc_pb');
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
// Init & Export
exports.myService = new MyserviceClient(
'http://localhost:3000',
credentials.createInsecure(),
);
(async () => {
await app.prepare();
const server = express();
server.get("*", (req, res) => handle(req, res));
server.listen(process.env.PORT, () => {
console.log(`Listening at http://localhost:${process.env.PORT}`);
});
})();
And then use it on the homepage, for example:
import React from 'react';
const { GetSmthRequest } = require('../gen/myservice_pb');
const { myService } = require('../server.js');
const IndexPage = () => (
<div>
<span>My HomePage</span>
</div>
)
const getServerSideProps = async () => {
const request = new GetSmthRequest();
request.setSomeStuff('random');
myService.getStmh(GetSmthRequest, (err, res) => {
//...
})
return {
props: {
}
}
}
export default IndexPage;
But for some reason it's not possible to initialize the client service in the server.js.
Also I tried doing it with next.config.js:
const { credentials } = require('#grpc/grpc-js');
const { MyserviceClient } = require('./gen/myservice_grpc_pb');
module.exports = {
serverRuntimeConfig: {
myService: new MyserviceClient(
'http://localhost:3000',
credentials.createInsecure(),
),
},
};
This solution works, so I can use the service through serverRuntimeConfig, thereby initializing it only once in the entire application, but when I make a request somewhere using getServerSideProps, I get an error:
Request message serialization failure: Expected argument of type ...
Error explanation: (https://stackoverflow.com/a/50845069/9464680)
That error message indicates that message serialization
(transformation of the message object passed to gRPC into binary data)
failed. This generally happens because the message object doesn't
match the expected message type or is otherwise invalid
Does anyone know why I am getting this error?
It's also interesting to see some examples of using Next.js with grpc-node.
For such a case you can use Node.js global
I'm using NextJs 10.0.5 with next-i18next 8.1.0 to localize my application. As we all know nextJs 10 has subpath routing for internationalized routing. In addition, I need to change the page names by language. For example, I have a contact-us file inside the pages folder. When I change the language to Turkish, I have to use localhost:3000/tr/contact-us. However, I want to use localhost:3000/bize-ulasin to access the contact-us page when the language is Turkish. So there are two URLs and only one page file.
It works when I use custom routing with express js in the server.js file. However, when I want to access the "locale" variable within the getStaticProps function in the contact-us file, I cannot access it. The getStaticProps function returns undefined for "locale" variable when I use localhost:3000/bize-ulasin URL.
server.js
const { createServer } = require("http");
const { parse } = require("url");
const next = require("next");
const app = next({ dev: process.env.NODE_ENV !== "production" });
const handle = app.getRequestHandler(app);
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
const { pathname, query } = parsedUrl;
if (pathname === "/bize-ulasin") {
app.render(req, res, "/contact-us", query);
}else{
handle(req, res, parsedUrl);
}
}).listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
});
/pages/contact-us-file
import { Fragment } from "react";
import Head from "next/head";
import { useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
const ContactUs = () => {
const { t } = useTranslation("common");
return (
<Fragment>
<Head>
<title>Contact-Us</title>
</Head>
</Fragment>
);
};
export const getStaticProps = async ({ locale }) => {
console.log(locale); // When I use the URL localhost: 3000/bize-ulasin, it returns undefined.
return {
props: {
...(await serverSideTranslations(locale, ["common"])),
},
};
};
export default ContactUs;
How can I access the "locale" variable with getStaticProps? Or, how can I use the following URLs with the same page file?
->localhost:3000/contact-us
->localhost:3000/bize-ulasin
I also faced the same problem today. That's how I solved the issue.
First of all, delete the server.js file. With Next.JS 10, using server.js will create conflict with the i18n routes and you won't be able to get the locale data in getStaticProps.
NextJS has a beautiful method named rewrites. We will use that instead of our server.js file. For example, if you have a page named contact-us-file, we can rewrite our next.config.js file as
const { i18n } = require('./next-i18next.config')
module.exports = {
i18n,
async rewrites() {
return [
{
source: '/contact-us',
destination: '/en/contact-us-file',
},
{
source: '/bize-ulasin',
destination: '/tr/contact-us-file',
},
]
},
}
As you are already using Next-i18next, I hope you are familiar with the file that I am importing.
Now If you try to navigate localhost:3000/contact-us and localhost:3000/bize-ulasin you should be able to access your contact us page.
I'm reading the book introducing NodeJS with a simple web application example. The requirement in the example is that there are several data store classes in its own module, and we need to adopt the data store dynamically by setting environment variable. The code snippets of the example is something like following:
// memory-store.mjs
// The data store for storing data in memory
export default class MemoryStore {
// Some CRUD operation
}
// fs-store.mjs
// The data store for storing data into file system
export default class FSStore {
// Some CRUD operation
}
// store.mjs
// Provide a async function to import data store dynamically and
// set the instance to variable store, which is exported
let store;
async function load() {
try {
const moduleName = process.env.MODULE_NAME ?? 'memory';
const storeModule = await import(`./${moduleName}-store.mjs`);
const storeClass = storeModule.default;
store = new storeClass();
return store;
} catch(err) {
throw new Error('Something goes wrong...');
}
}
export { load, store };
// app.mjs
// Import the function to load the data store dynamically and
// the exported store for fetching data list
import express from 'express';
import { load, store } from './store.mjs';
const app = express();
load()
.then(store => {})
.catch(err => console.error(`Exception with error: ${err}`));
app.use('/', (req, res, next) => {
const dataList = store.retrieveAll();
res.send(dataList);
});
The code snippets above is not same as the one in the book overall. But the concept is same. It works fine in my local environment, but I'm wondering isn't there any problem if the request is coming and handled before the data store is imported due that the import function is async operation? Are there other solutions which can fulfill the requirement? Or I'm just missing something that the example from the book is just masterpiece? Thanks in advance!
If you want to guarantee that store has been initialized before any requests are handled by your express app, you could set up the express listener after the load promise has resolved. This would be as simple as the following:
import express from 'express';
import { load, store } from './store.mjs';
const app = express();
app.use('/', (req, res, next) => {
const dataList = store.retrieveAll();
res.send(dataList);
});
load()
.then(() => {
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});
})
.catch(err => console.error(`Exception with error: ${err}`));
So using axios, I am attempting to make a request to my express server, here is my Axios request:
/* #flow */
import type {
Dispatch,
GetState,
ThunkAction,
Reducer,
} from '../../types';
export const USERS_INVALID = 'USERS_INVALID';
export const USERS_REQUESTING = 'USERS_REQUESTING';
export const USERS_FAILURE = 'USERS_FAILURE';
export const USERS_SUCCESS = 'USERS_SUCCESS';
export const API_URL = '/api/articleList';
// Export this for unit testing more easily
export const fetchUsers = (axios: any, URL: string = API_URL): ThunkAction =>
(dispatch: Dispatch) => {
dispatch({ type: USERS_REQUESTING });
return axios.get(URL)
.then((res) => {
dispatch({ type: USERS_SUCCESS, data: res.data });
})
.catch((err) => {
dispatch({ type: USERS_FAILURE, err });
});
};
// Preventing dobule fetching data
/* istanbul ignore next */
const shouldFetchUsers = (state: Reducer): boolean => {
// In development, we will allow action dispatching
// or your reducer hot reloading won't updated on the view
if (__DEV__) return true;
const userListFetch = state.userListFetch;
if (userListFetch.readyStatus === USERS_SUCCESS) return false; // Preventing double fetching data
return true;
};
/* istanbul ignore next */
export const fetchUsersIfNeeded = (): ThunkAction =>
(dispatch: Dispatch, getState: GetState, axios: any) => {
/* istanbul ignore next */
if (shouldFetchUsers(getState())) {
/* istanbul ignore next */
return dispatch(fetchUsers(axios));
}
/* istanbul ignore next */
return null;
};
and here is my code on the express server:
//GET ARTICLES
app.get('/api/articleList', (req, res) => {
console.log('hello');
});
It does not log "hello" and it displays no errors, I just think I'm missing something for the Axios to reach my server...
I had something similar working on another app but cannot seem to implement it here, I've searched online and cannot find a solution, any help or advice is appreciated - thank you in advance!
NOTE: the Axios request works fine when the "API_URL" variable is
set to a myjson url link, so I know my action works fine, I just feel
like I'm missing something for it to reach my server
EDIT: Please see my entire server.js:
/* #flow */
import path from 'path';
import morgan from 'morgan';
import express from 'express';
import compression from 'compression';
import helmet from 'helmet';
import hpp from 'hpp';
import favicon from 'serve-favicon';
import React from 'react';
import { renderToString, renderToStaticMarkup } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import { matchRoutes } from 'react-router-config';
import { Provider } from 'react-redux';
import chalk from 'chalk';
import createHistory from 'history/createMemoryHistory';
import configureStore from './redux/store';
import Html from './utils/Html';
import App from './containers/App';
import routes from './routes';
import { port, host } from './config';
const app = express();
// Using helmet to secure Express with various HTTP headers
app.use(helmet());
// Prevent HTTP parameter pollution.
app.use(hpp());
// Compress all requests
app.use(compression());
// Use morgan for http request debug (only show error)
app.use(morgan('dev', { skip: (req, res) => res.statusCode < 400 }));
app.use(favicon(path.join(process.cwd(), './build/public/favicon.ico')));
app.use(express.static(path.join(process.cwd(), './build/public')));
// Run express as webpack dev server
if (__DEV__) {
const webpack = require('webpack');
const webpackConfig = require('../tools/webpack/webpack.client.babel');
const compiler = webpack(webpackConfig);
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
hot: true,
noInfo: true,
stats: 'minimal',
}));
app.use(require('webpack-hot-middleware')(compiler));
}
// Register server-side rendering middleware
app.get('*', (req, res) => {
if (__DEV__) webpackIsomorphicTools.refresh();
const history = createHistory();
const store = configureStore(history);
const renderHtml = (store, htmlContent) => { // eslint-disable-line no-shadow
const html = renderToStaticMarkup(<Html store={store} htmlContent={htmlContent} />);
return `<!doctype html>${html}`;
};
// If __DISABLE_SSR__ = true, disable server side rendering
if (__DISABLE_SSR__) {
res.send(renderHtml(store));
return;
}
// Load data on server-side
const loadBranchData = () => {
const branch = matchRoutes(routes, req.url);
const promises = branch.map(({ route, match }) => {
// Dispatch the action(s) through the loadData method of "./routes.js"
if (route.loadData) return route.loadData(store.dispatch, match.params);
return Promise.resolve(null);
});
return Promise.all(promises);
};
// Send response after all the action(s) are dispathed
loadBranchData()
.then(() => {
// Setup React-Router server-side rendering
const routerContext = {};
const htmlContent = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={routerContext}>
<App />
</StaticRouter>
</Provider>,
);
// Check if the render result contains a redirect, if so we need to set
// the specific status and redirect header and end the response
if (routerContext.url) {
res.status(301).setHeader('Location', routerContext.url);
res.end();
return;
}
// Checking is page is 404
const status = routerContext.status === '404' ? 404 : 200;
// Pass the route and initial state into html template
res.status(status).send(renderHtml(store, htmlContent));
})
.catch((err) => {
res.status(404).send('Not Found :(');
console.error(`==> 😠Rendering routes error: ${err}`);
});
});
//----------------------------------------------------
//GET ARTICLES
app.get('/api/articleList', (req, res) => {
console.log('yoyoyo');
var indexLimit = parseInt(req.query.indexLimit, 10);
var articleId = req.query.articleId
var articles = [];
db.collection('articles')
.find()
.sort("dateAdded", -1)
.limit(indexLimit)
.toArray()
.then(result => {
articles = articles.concat(result);
}).then(() => {
res.send(articles);
}).catch(e => {
console.error(e);
});
});
//------------------------------------
//connect to mongo db
var db
const MongoClient = require('mongodb').MongoClient
MongoClient.connect('mongodb://##CHANGED###:test#ds123930.mlab.com:###/###', (err, database) => {
if (err) return console.log(err);
db = database
console.log('db connected');
})
if (port) {
app.listen(port, host, (err) => {
if (err) console.error(`==> 😠OMG!!! ${err}`);
console.info(chalk.green(`==> 🌎 Listening at http://${host}:${port}`));
// Open Chrome
require('../tools/openBrowser').default(port);
});
} else {
console.error(chalk.red('==> 😠OMG!!! No PORT environment variable has been specified'));
}
You will need to move your /api routes above:
app.get('*', (req, res) => {
...
}
Your call to /api/articleList is hitting that catch all route handler of '*' and responding to the request with the rendered page. When communicating with your api for data, you don't want a page render, you want the response from the api :)
Middleware is executed in order of appearance from top to bottom.
If I send POST such /image/cover or /image/sub/ from client, the router function doesn't work at all so It sends 404. It's supposed to work but I literally have no idea. I never had this case It just doesn't work for no reason.
router
import Router from 'koa-router'
const router = new Router({ prefix: '/image' })
router.post('/cover', async (ctx, next) => {
let URLpath = ctx.request.body.files.cover.path
ctx.body = { url: URLpath }
})
router.post('/sub', async (ctx, next) => {
let URLpath = ctx.request.body.files.sub.path
ctx.body = { url: URLpath }
})
export default router
log
<-- POST /image/cover
--> POST /image/cover 404 33ms -
import code (UPDATED)
router/index.js
import compose from 'koa-compose'
import accountRouter from './account'
import mypageRouter from './mypage'
import imageRouter from './image'
import postRouter from './post'
const routes = [
accountRouter,
mypageRouter,
imageRouter,
postRouter
]
export default () => compose([].concat(
...routes.map(r => [r.routes(), r.allowedMethods()])
))
app.js
import Koa from 'koa'
import serve from 'koa-static'
import logger from 'koa-logger'
import views from 'koa-views'
import session from 'koa-session'
import passport from 'koa-passport'
import bodyParser from 'koa-body'
import path from 'path'
import routes from './routes'
import config from './config'
import form from './util/formidable'
import mongoose from 'mongoose'
mongoose.Promise = global.Promise
mongoose
.connect(config.MONGODB_URI)
.then(startApp).catch(::console.error)
function startApp() {
const app = new Koa()
const port = process.env.PORT || 3000
const dist = __dirname + '/views/'
console.log(form)
const bpOption = { IncomingForm: form, multipart: true }
app.keys = ['secret', 'key'];
require('./util/passport')
app
.use(logger())
.use(serve(dist))
.use(session({}, app))
.use(bodyParser(bpOption))
.use(passport.initialize())
.use(passport.session())
.use(views(__dirname+'/views', { extension: 'pug'}))
.use(routes())
app.listen(port, () => console.log(`[!] Server is Running on ${port}`))
}
you need help function:
// #help function record route table map
'use strict';
const fs = require('fs');
const resolve = require('path').resolve;
module.exports = function record(router, filename) {
const routeTable = fs.createWriteStream(resolve(__dirname, filename));
routeTable.write(`Update Date: ${new Date().toString()}\n\n`);
for (let len = router.stack.length, i = 0; i < len; i += 1) {
const route = router.stack[i];
routeTable.write(`URL:${route.path}\t${route.methods.join('|')}\n`);
}
routeTable.end();
};
then you can involve it by
const record = require('./record');
record(routeApp, '.routerc')
you will get .routerc file(route map) with content such as:
URL: /a/c/c POST
URL: /b/a/d GET
it can help you to solve problem. maybe work. good luck!