SSR Nuxt.js with integreted REST API backend - node.js

I'm developing an SSR Nuxt.js app with an integrated REST API server.
To do this, I've added my /api endpoint inside Nuxt server.js code as following
const express = require('express')
const consola = require('consola')
const { Nuxt, Builder } = require('nuxt')
const app = express()
// Import and Set Nuxt.js options
const config = require('../nuxt.config.js')
config.dev = process.env.NODE_ENV !== 'production'
// MY REST API ENDPOINT (It's the right approach?)
const routesApi = require('./api/routes')
app.use('/api', routesApi)
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
const { host, port } = nuxt.options.server
await nuxt.ready()
// Build only in dev mode
if (config.dev) {
const builder = new Builder(nuxt)
await builder.build()
}
// Give nuxt middleware to express
app.use(nuxt.render)
// Listen the server
app.listen(port, host)
consola.ready({
message: `Server listening on http://${host}:${port}`,
badge: true
})
}
start()
I didn't found examples related to this approach.
I need some help to understand if it's the right way.
Thank you for your support.

You would probably like to read following article: https://blog.lichter.io/posts/nuxt-with-an-api/
It has common takes to solve "API with Nuxt" case.
Your solution is already enough for small integrated API, I guess, that way you avoid setting up proxy against CORS issues. :) You could add some sugar with serverMiddleware:
// nuxt.config.js
export default {
...
serverMiddleware: [
'/api': '~/api/index.js'
],
...
}
// api/index.js
export default function (req, res, next) {
... // Well, here comes nothing
next()
}
But big API scales nice on separate server, it's also a separation of concerns to consider. Nuxt serves better as universal app rendering middleware, but API can be written even in another language, backend. For defying problems with CORS, you'll need to place /api on same domain, as you intended, thus it's more easier with Nuxt proxy-module.

Related

Jest and supertest: Test keep exceeding timeout

Hello I am a bit confused by this error I have encountered.
I am working on an Universal React App using Webpack 5 and Express.
I want to implement Jest support by using the React-testing-Library for the frontend (which work) and supertest for the backend (this is where I am blocked).
I am following this basic tutorial recommended by the jest doc himself in order to use jest on an node express environment.
But everytime I get this error:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
Here are my code:
server.js
import app from './app.js';
import { mongooseConnection, disconnectMongoDB } from "./routers/services/url/urlDB.js"; // we call mongooseConnect var to connect only once into the mongoDB database
const PORT = process.env.PORT || 8080;
// the server listen on the port set by node on localhost.
app.listen(PORT, () => {
console.log(
`Server listening on \x1b[42m\x1b[1mhttp://localhost:${PORT}\x1b[0m in \x1b[41m${process.env.NODE_ENV}\x1b[0m`,
);
});
// when when we shut down the app we execute a callback function before closing the server
process.on('exit', function() {
disconnectMongoDB();
});
app.js
import express from 'express';
import path from 'path';
import cors from 'cors';
import {envIsProduction, envIsDevelopment} from './envmode/envUtil.js';
import { enableHMR } from './reload/hotReload.js';
let app = express();
// if we have set the environnent on production then:
if (envIsProduction()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( PRODUCTION ) |");
console.log("|_______________________________________|");
console.log(" ");
app.use(express.static(path.join(__dirname,'../client'))); // we serve static file like the bundle-app.js to the browser from the current directory where the server is executed and we move to the top root to access the file
}
else if (envIsDevelopment()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( DEVELOPMENT ) |");
console.log("|_______________________________________|");
console.log(" ");
enableHMR(app); // we enable the Hot MPodule Reload on the frontend and the backend
}
app.use(cors());
app.use(express.urlencoded({extended:false}));
app.use(express.json());
//Hot reload!
//ALL server routes are in this module!
app.use((req, res, next) => {
require("./routers/routers")(req, res, next);
});
export default app;
routers.js
import renderPage from "./renderpage/renderPage.js";
import { serverRoutes, reactRouterRoutes, getReactRouterRoutesString } from "./routes.js";
import express from "express";
import routerLoginDB from "./request/routerLoginDB.js";
import routerSignupDB from "./request/routerSignupDB.js";
const router = express.Router();
// Put all your server routes in here
// When the user connect to the root of the server we send the page
router.get(serverRoutes.root, renderPage);
// When the user send a get request by the /click route a console.log and a respone is send.
router.get(serverRoutes.click, (req, res)=>{
res.status(200).send("Click");
});
// when this user want to login into his account, we ask for the routerLoginDB to handle it
router.post(serverRoutes.login,routerLoginDB);
// when this user want to signup into his account, we ask for the routerSignupDB to handle it
router.post(serverRoutes.signup, routerSignupDB);
// For all the routes that only react-router need to use, if we refresh on a nested route of the react-router from the client side then we redirect it to the root route "/"
router.get(reactRouterRoutes,(req,res) => {
res.redirect("/");
});
router.get("*", (req,res) =>{
res.status(404).send('page not found');
}); //For all other type of request excluding the one specified here, we send back a 404 page;
module.exports = router;
app.test.js
import request from '../utils/test-node-utils.js'
describe("Test the /click path", () => {
test("It should response the GET method", () => {
return request
.get("/click")
.expect(200);
});
});
and finally test-node-utils.js
import supertest from "supertest";
import app from "../serverside/app.js";
const request = supertest(app);
export default request;
Don't believe what the error say because I think it is more deep than that.
I have tried to increased the jest timeout value but it keep being stuck and reach the timeout limit.
I have done exactly like the tutorial say without using my project structure and it worked but when I try to implement the tutorial in my backend structure, it don't work with supertest.
I think it is related to my files or backend structure that make it don't work with the test.
Thanks in advance for your help
I've recently debugged a similar issue where my Jest tests would run successfully (or not) in my dev. environment but when I would try and package the app as a Docker image all my tests would time out.
It turned out that by commenting out the line which setup CORS, which for me I only turned on in production builds (should have been a clue), the tests started to run again when building the image.
...
const NODE_ENV = process.env.NODE_ENV;
const app = express();
NODE_ENV.toUpperCase() === 'PRODUCTION' && app.use(cors);
...
I mentioned this as I can see from your snippet above that you are also using the cors middleware and that, in your case, it's set all the time.
Perhaps not your issue, and you may want CORS in your tests for some reason, but try commenting it out and see if your tests run.

MSAL with Express and React: Authentication for root URL

I have a React frontend together with an express backend.
The goal is to only allow access to the frontend when the user was successfully authenticated by the express backend which uses MSAL (Microsoft Authentication Library).
Originally, I implemented the authentication flow by follwing this official Microsoft guide. However, this guide is only about using pure express without a real frontend. So I had to combine the information of this guide with my React frontend.
Step 1 was to run them both at the same port (localhost:3000) by building the frontend into a "build" folder and telling express to use the static files in this folder. This worked fine.
But now I am stuck with following problem: I want the authentication to be done when visiting localhost:3000. But currently, this URL is accessed without authentication. After the app.use(), app.get() is not called. It works only when app.get() is called with a somehow extended URL such as /login. Then the user will be authenticated and then redirected to localhost:3000.
Please see the express code:
//server.js
const path = require('path');
const express = require("express");
const msal = require('#azure/msal-node');
const SERVER_PORT = process.env.PORT || 3000; // use static build
const REDIRECT_URI = "http://localhost:3000";
// msal config
const config = {
auth: {
clientId: "xyz",
authority: xyz",
clientSecret: "xyz"
},
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
};
// Create msal application object
const pca = new msal.ConfidentialClientApplication(config);
// Create Express App and Routes
const app = express();
// production mode: Build frontend using npm run build --> creates build folder. Use "build"-folder to serve static files with express
// use build folder
app.use(express.static(path.join(__dirname, './build')));
app.get('/', (req, res) => { // "/": app.get() is not invoked. "/login": works fine
const authCodeUrlParameters = {
scopes: ["user.read"],
redirectUri: REDIRECT_URI,
};
// get url to sign user in and consent to scopes needed for application
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => {
res.redirect(response);
}).catch((error) => console.log(JSON.stringify(error)));
});
// currently not being invoked
app.get('/redirect', (req, res) => {
const tokenRequest = {
code: req.query.code,
scopes: ["user.read"],
redirectUri: REDIRECT_URI,
};
pca.acquireTokenByCode(tokenRequest).then((response) => {
console.log("\nResponse: \n:", response);
res.sendStatus(200);
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
});
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
Why is app.get() (= the authentication flow) not invoked when using "/"? "*" does not work, too. Is it even possible to achieve the goal to do the authentication on localhost:3000 instead of localhost:3000/login?
If it is not possible, how can I prevent the user from accessing the frontend by just typing localhost:3000?
I also searched StackOverflow for this, but with no success.This question for example does not really work out for me, since it uses extra private routes. But I would like to avoid extra routes. Because I find so many examples that do exactly this, I start wondering if it's the only possible way.
Help will be appreciated.
To allow access to the frontend when the user was successfully authenticated by the express backend you can use react-router-guards.
Learn more here: https://www.npmjs.com/package/react-router-guards

Can you call "express()" more than once in an ExpressJS application? (Or: what exactly is "express()" doing?)

I've been using Express for a while but suddenly I'm unsure about something pretty basic --
I'm trying to add custom middleware to a KeystoneJS application -- specifically I'm adding a JWT token endpoint to a TinyMCE custom field
The custom field is
export let Wysiwyg = {
type: 'Wysiwyg',
implementation: Text.implementation,
views: {
Controller: Text.views.Controller,
Field: importView('./views/Field'),
Filter: Text.views.Filter,
},
adapters: Text.adapters,
prepareMiddleware,
};
and prepareMiddleware is
function prepareMiddleware() {
const tinymcePath = dirname(require.resolve('tinymce'));
const app = express();
app.use(cors());
app.use('/tinymce-assets', express.static(tinymcePath));
app.post('/jwt', function (req, res) {
// NOTE: Before you proceed with the TOKEN, verify your users' session or access.
const payload = {
sub: '123', // Unique user id string
name: 'John Doe', // Full name of user
// Optional custom user root path
// 'https://claims.tiny.cloud/drive/root': '/johndoe',
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 60 minutes expiration
};
try {
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256'});
res.set('content-type', 'application/json');
res.status(200);
res.send(JSON.stringify({
token: token
}));
} catch (e) {
res.status(500);
res.send(e.message);
}
});
return app;
}
This is all called from a KeystoneJS app that has its own ExpressJS server running. What exactly is the call to express() above doing? The ExpressJS API docs say
**express()**
Creates an Express application. The express() function is a top-level function exported by the express module.
var express = require('express')
var app = express()
I always understood this to be creating a new HTTP server. Surely you don't want to do that twice in a single app unless you're serving on different ports (which I'm not trying to do)?
Similarly, the KeystoneJS docs say
If you need to provide your own custom middleware for your system you
can create a custom App and include it in your exported apps.
class CustomApp {
prepareMiddleware({ keystone, dev, distDir }) {
const middleware = express();
// ...
return middleware;
}
}
Here, again, they're calling express().
What exactly happens when you callexpress()? It starts a new Express app, according to the docs, but I always thought this was equivalent to starting a new server. Surely, I thought, you can't start two servers on the same port.
Thanks for helping clear up my confusion -- I'm obviously not seeing the forest for the trees.
express() basically just creates a stack of middleware functions. It's not a server on its own.
Because it's just a stack of middleware, an Express app can be 'mounted' into another app. An example is shown here (edited for brevity):
var sub2 = express();
sub2.get("/", (req, res) => { res.json({}); });
var app = express();
app.use("/foo", sub2);
Defining and use()ing a new Express instance is really no different from loading any other middleware stack, such as express.Router().
As for binding to ports, usually, you'll only call the listen() helper function on the upper-most Express app instance. All this does is set up a basic HTTP server (so you don't have to) and registers your Express instance as the callback. It's little different from doing http.createServer(options, myUpperMostExpressApp);.

swagger-ui-express Multiple Routes for Different API Documentation

I have 2 separate swagger API documentations which I want to run via swagger-ui-express NPM package, and my express server is starting fine on port 5000, but when I am trying to access any of the URL always getting the 404 error, Here is my app.js file and URL's for your reference:
Route 1: http://localhost:5000/edi
Route 2: http://localhost:5000/ecom
const express = require('express');
const router = require('express').Router();
const swaggerUi = require('swagger-ui-express');
const ediSwaggerDocument = require('./edi-openapi.json');
const ecomSwaggerDocument = require('./ecom-openapi.json');
const SWAGGER_APP_PORT = process.env.SWAGGER_APP_PORT || 5000;
const app = express();
// Route Middleware to be called before serving Any Route
router.use('/', swaggerUi.serve);
// Route - EDI RESTful API Documentaion
router.get('/edi', swaggerUi.setup(ediSwaggerDocument));
// Route - eCommerce RESTful API Documentaion
router.get('/ecom', swaggerUi.setup(ecomSwaggerDocument));
app.listen(SWAGGER_APP_PORT, () => console.log(`RESTful API Up and Running on Port ${SWAGGER_APP_PORT}`));
Try the following configurations to hook swaggerUi with express-app
app.use("/edi", swaggerUi.serve, (...args) => swaggerUi.setup(ediSwaggerDocument)(...args));
app.use("/ecom", swaggerUi.serve, (...args) => swaggerUi.setup(ecomSwaggerDocument)(...args));
I haven't dig enough into the swagger-ui-express but i think the problem comes from the function generateHTML (called in swaggerUi.setup). A global module variable (swaggerInit) is updated when called.
So the last call to generateHTML has side effects on every routes that use swaggerUi.setup middleware.
A quick fix is to generate HTML each time the route is called. According to the code snippet you provide, it should looks like :
let swaggerDocEdi = require('./edi-openapi.json');
let swaggerDocEcom= require('./ecom-openapi.json');
let router = express.Router();
router.use('/api/edi', swagger.serve, (req, res) => {
let html = swagger.generateHTML(swaggerDocEdi);
res.send(html);
});
router.use('/api/ecom', swagger.serve, (req, res) => {
let html = swagger.generateHTML(swaggerDocEcom);
res.send(html);
});
Note that the global variable is still updated.
router.use('/jobs/api/:id',swaggerUi.serve,(req,res)=>{
console.log("here")
let a = req.params.id
if(a==='all'){ res.status(200).send(swaggerUi.generateHTML(swaggerDocument))}
if(a==='google'){res.status(200).send(swaggerUi.generateHTML(GoogleAds)) }
});
I was also looking for a solution for this issue, and found a better solution recommended by Swagger UI Express. check the link below
https://github.com/scottie1984/swagger-ui-express#two-swagger-documents
const express = require('express');
const app = express();
const swaggerUi = require('swagger-ui-express');
const swaggerDocumentOne = require('./swagger-one.json');
const swaggerDocumentTwo = require('./swagger-two.json');
var options = {}
app.use('/api-docs-one', swaggerUi.serveFiles(swaggerDocumentOne, options), swaggerUi.setup(swaggerDocumentOne));
app.use('/api-docs-two', swaggerUi.serveFiles(swaggerDocumentTwo, options), swaggerUi.setup(swaggerDocumentTwo));
Hope this will help others as well.
It looks like the Router is being used incorrectly. For this simple use case I would recommend adding your routes directly to the app instance. See:
const express = require('express');
// xxxx const router = require('express').Router();
const swaggerUi = require('swagger-ui-express');
const ediSwaggerDocument = require('./edi-openapi.json');
const ecomSwaggerDocument = require('./ecom-openapi.json');
const SWAGGER_APP_PORT = process.env.SWAGGER_APP_PORT || 5000;
const app = express();
// Route Middleware to be called before serving Any Route
app.use('/', swaggerUi.serve); // replaced router with app
// Route - EDI RESTful API Documentaion
// REPLACED "router" with "app"
app.get('/edi', swaggerUi.setup(ediSwaggerDocument));
// Route - eCommerce RESTful API Documentaion
// REPLACED "router" with "app"
app.get('/ecom', swaggerUi.setup(ecomSwaggerDocument));
app.listen(SWAGGER_APP_PORT, () => console.log(`RESTful API Up and Running on Port ${SWAGGER_APP_PORT}`));
Now, you could use the Router by adding it to the app instance with app.use(). See:
// Route - EDI
router.get('/edi', swaggerUi.setup(ediSwaggerDocument));
// Route - eCommerce
router.get('/ecom', swaggerUi.setup(ecomSwaggerDocument));
// Adding it to App instance
app.use('/swagger', router)
// End Point => localhost:5000/swagger/edi
Hope this helps!!

What is the convention of separating APIs as modules in NodeJS?

Here is how my app.js looks like...
I want to know how do I separate this single file into multiple files for better code quality. How do we do that?
My idea is to have the below files...
server.js -> for server related properties
serve.js -> this becomes the main file in the package.json
apis -> this shall be the file with apis, I shd be able to separate apis depending upon the modules too...
What is the preferred convention in NodeJS? I am sure that we do not want to write all apis in one single file.
const express = require('express') // import express module
const app = express() // initiate express app
app.use(express.json()) // not sure what this is but without it POST cant read the JSON parameters from the body
const host = 'localhost' // host
const port = process.env.PORT || 1338 // pick port
const routePrefix = '/' + 'api' + '/' // this is the route prefix used from where the APIs will be accesssed
const routes = { // define routes
root: routePrefix + 'root',
test: routePrefix + 'test',
items: routePrefix + 'items',
item: routePrefix + 'items/:id'
}
// print details
function printDetails(currentRoute, requestMethod, requestParams,
requestQuetyString) {
console.log(currentRoute, requestMethod, requestParams, requestQuetyString);
}
// get root
app.get(routes.root, (req, res) => {
printDetails(routes.root, req.method, req.params, req.query)
res.send(routes.root)
})
// get test route
app.get(routes.test, (req, res) => {
printDetails(routes.test, req.method, req.params, req.query)
res.send(routes.test)
})
// for the web server
app.use(express.static('../public')) // this is where static files reside and need to be served to for the clientside app
// start the API server and Web server
app.listen(port, () => {
console.log(`
\nExpress Server started on port ${port}..
APIs can be accessed at http://${host}:${port}${routePrefix}
Web Server started on port http://${host}:${port}
`)
})
I have tried it on my own and the respective files look like this. However, I am unable to run this.
server.js
const express = require('express') // import express module
const app = express() // initiate express app
app.use(express.json()) // not sure what this is but without it POST cant read the JSON parameters from the body
//const api = require('./apis')
//const app = api.app
const host = 'localhost' // host
const port = process.env.PORT || 1338 // pick port
const routePrefix = '/' + 'api' + '/' // this is the route prefix used from where the APIs will be accesssed
const routes = { // define routes
root: routePrefix + 'root',
test: routePrefix + 'test',
items: routePrefix + 'items',
item: routePrefix + 'items/:id'
}
// for the web server
app.use(express.static('../public')) // this is where static files reside and need to be served to for the clientside app
module.exports = {
app: app,
host: host,
port: port,
routePrefix: routePrefix,
routes: routes
}
serve.js
const server = require('./server') // import server module
//const app = server.app
// start the API server and Web server
server.app.listen(server.port, () => {
console.log(`
\nExpress Server started on port ${server.port}..
APIs can be accessed at http://${server.host}:${server.port}${server.routePrefix}
Web Server started on port http://${server.host}:${server.port}
`)
})
api.js
'use strict'
const server = require('./server') // import sever module
const app = server.app
// get test route
app.get(server.routes.test, (req, res) => {
printDetails(server.routes.test, req.method, req.params, req.query)
res.send(server.routes.test)
})
module.exports = {
}
The problem I am facing is how do I use module.exports and what do I need to export from what module. My requirement is when I run "node serve.js", I should be able to run the APIs and they should be available for the client. How do I achieve this?
I think that the following structure is much more maintainable and easier to understand (also this is the more commonly used structure in new web apps in node):
├───client <-- Your web client application directory
│ └───assets
├───common <-- Common (shared) files between client and server
└───server <-- Your SugoiJS server directory
├───config <-- Build config (environment, webpack)
│ └───webpack
└───src <-- Your server app source code
├───app <-- Bootstrap module, Server initialize and listener files, 'authorization' class(optional)
│ └───classes
├───config <-- Server configuration (services, paths, etc.)
└───modules <-- All of you application modules
└───index <-- Single module
├───controllers <-- Modules' controllers
├───models <-- Modules' models(optional)
└───services <-- Modules' services
This is taken from SugoiJS framework.
https://wiki.sugoijs.com/get-started
Then basically all the routes you are writing will be controllers :)
There are many ways to split up JS code into files, the simplest way (that is still quite efficient) is to require modules. Here's a full guide, and here's a minimal demo:
// lib.js
module.exports = {
x: 10,
y: () => 20
}
// index.js
const lib = require('./lib');
console.log(lib.x); // 10
console.log(lib.y()); // 20
To keep the code clear, simple and efficient, requiring a module should not have any side effects (ie deleting a file, or making requests): for example, define a Class inside a module, and export it.
There are other ways to split up code between files, such as import/export.

Resources