Accessing Loopback config data from middleware - node.js

Say we are in Loopback middleware, such as
app.use(function(req,res,next){
// I am not sure what key to use here in the getter...
const config = app.get('?');
});
I want to access the overall config that Loopback is using.
I tried:
const config = app.get('config');
const config = app.get('env');
const config = app.get('datasources');
nothing gives me quite what I want.
Interestingly, this gives me:
console.log(app.get('restApiRoot'));
=> '/api'
so that's a clue to what's going on, but I want to get the parent object(s) for the above data.
how can we access the configuration that Loopback has loaded. The configuration of course varies by environment variables etc.
I want to log what datasources.x.js file was loaded and what config.x.js file was loaded, and any other server configuration info I can capture.
Having a lot of trouble figuring out how to do this.
This seems to be the same question I have:
https://github.com/strongloop/loopback/issues/1526
but they point me to the void that is Google Groups and I searched through there and couldn't find what the answer to this question.

This behavior is actually inherited from Express.
The entire config is stored in the app.settings object, with app.get(key) and app.set(key,value) just acting as getter/setter.
Doing console.log(app.settings); (in server/server.js for instance) it on a fresh loopback install returns the following:
{ 'x-powered-by': true,
etag: 'weak',
'etag fn': [Function: wetag],
env: 'development',
'query parser': 'extended',
'query parser fn': [Function: parseExtendedQueryString],
'subdomain offset': 2,
'trust proxy': false,
'trust proxy fn': [Function: trustNone],
view: [Function: View],
views: 'C:\\Users\\*******\\Documents\\GitHub\\lbtest\\views',
'jsonp callback name': 'callback',
host: '0.0.0.0',
port: 3000,
restApiRoot: '/api',
remoting:
{ context: { enableHttpContext: false },
rest: { normalizeHttpPath: false, xml: false },
json: { strict: false, limit: '100kb' },
urlencoded: { extended: true, limit: '100kb' },
cors: false,
errorHandler: { disableStackTrace: false } },
legacyExplorer: false,
'loopback-component-explorer': { mountPath: '/explorer' },
url: 'http://localhost:3000/' }

Related

Astro: How to proxy service calls

I am setting up an Astro site which will display data fetched from a simple service running on the same host but a different port.
The service is a simple Express app.
server.js:
const express = require('express')
const app = express()
const port = 3010
const response = {
message: "hello"
}
app.get('/api/all', (_req, res) => {
res.send(JSON.stringify(response))
})
app.listen(port, () => {
console.log(`listening on port ${port}`)
})
Since the service is running on port 3010, which is different from the Astro site, I configure a server proxy at the Vite level.
astro.config.mjs:
import { defineConfig } from 'astro/config';
import react from '#astrojs/react';
export default defineConfig({
integrations: [react()],
vite: {
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
server: {
proxy: {
'/api/all': 'http://localhost:3010'
}
}
},
});
Here is where I am trying to invoke the service.
index.astro:
---
const response = await fetch('/api/all');
const data = await response.json();
console.log(data);
---
When I run yarn dev I get this console output:
Response {
size: 0,
[Symbol(Body internals)]: {
body: Readable {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
stream: Readable {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 1,
_maxListeners: undefined,
_read: [Function (anonymous)],
[Symbol(kCapture)]: false
},
boundary: null,
disturbed: false,
error: null
},
[Symbol(Response internals)]: {
type: 'default',
url: undefined,
status: 404,
statusText: '',
headers: { date: 'Tue, 02 Aug 2022 19:41:02 GMT' },
counter: undefined,
highWaterMark: undefined
}
}
It looks like the network request is returning a 404.
I'm not seeing in the doc much more about server configuration.
Am I going about this the right way?
I have this working correctly with a vanilla Vite app and the same config/setup.
How can I proxy local service calls for an Astro application?
Short Answer
You cannot proxy service calls with Astro but also you don't have to
For direct resolution answer see section functional test without proxy
Details
Astro does not forward the server.proxy config to Vite (unless you patch your own version of Astro), the Astro Vite server config can be seen empty
proxy: {
// add proxies here
},
reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L125
there is a merge of Astro server with Astro vite.server config but it does not take the proxy param. This is not obvious to get from the code, see tests later.
let result = commonConfig;
result = vite.mergeConfig(result, settings.config.vite || {});
result = vite.mergeConfig(result, commandConfig);
reference https://github.com/withastro/astro/blob/8c100a6fe6cc652c3799d1622e12c2c969f30510/packages/astro/src/core/create-vite.ts#L167
Tests
Config tests
I tried all possible combinations of how to input config to Astro and in each location a different port number to show which one takes an override
a vite.config.js file on root with
export default {
server: {
port:6000,
proxy: {
'/api': 'http://localhost:4000'
}
}
}
in two locations in the root file astro.config.mjs
server
vite.server
export default defineConfig({
server:{
port: 3000,
proxy: {
'/api': 'http://localhost:4000'
}
},
integrations: [int_test()],
vite: {
optimizeDeps: {
esbuildOptions: {
define: {
global: 'globalThis'
}
}
},
server: {
port:5000,
proxy: {
'/api': 'http://localhost:4000'
}
}
}
});
in an Astro integration
Astro has a so called integration that helps update the config (sort of Astro plugins) the integration helps identify what was finally kept in the config and also gives a last chance to update the config
integration-test.js
async function config_setup({ updateConfig, config, addPageExtension, command }) {
green_log(`astro:config:setup> running (${command})`)
updateConfig({
server:{proxy : {'/api': 'http://localhost:4000'}},
vite:{server:{proxy : {'/api': 'http://localhost:4000'}}}
})
console.log(config.server)
console.log(config.vite)
green_log(`astro:config:setup> end`)
}
this is the output log
astro:config:setup> running (dev)
{ host: false, port: 3000, streaming: true }
{
optimizeDeps: { esbuildOptions: { define: [Object] } },
server: { port: 5000, proxy: { '/api': 'http://localhost:4000' } }
}
astro:config:setup> end
the proxy parameter is removed from astro server config, the vite config is visible but has no effect as it is overridden, and not forwarded to Vite
test results
dev server runs on port 3000 which is from Astro config server all other configs overridden
the fetch api fails with the error
error Failed to parse URL from /api
File:
D:\dev\astro\astro-examples\24_api-proxy\D:\dev\astro\astro-examples\24_api-proxy\src\pages\index.astro:15:20
Stacktrace:
TypeError: Failed to parse URL from /api
at Object.fetch (node:internal/deps/undici/undici:11118:11)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
functional test without proxy
Given that Astro front matter runs on the server side, in SSG mode during build and in SSR mode on page load on the server then the server sends the result html, Astro has access to all host ports and can directly use the service port like this
const response = await fetch('http://localhost:4000/api');
const data = await response.json();
console.log(data);
The code above runs as expected without errors
Reference Example
All tests and files mentioned above are available on the reference example github repo : https://github.com/MicroWebStacks/astro-examples/tree/main/24_api-proxy
You can add your own proxy middleware with the astro:server:setup hook.
For example use http-proxy-middleware in the server setup hook.
// plugins/proxy-middleware.mjs
import { createProxyMiddleware } from "http-proxy-middleware"
export default (context, options) => {
const apiProxy = createProxyMiddleware(context, options)
return {
name: 'proxy',
hooks: {
'astro:server:setup': ({ server }) => {
server.middlewares.use(apiProxy)
}
}
}
}
Usage:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import proxyMiddleware from './plugins/proxy-middleware.mjs';
// https://astro.build/config
export default defineConfig({
integrations: [
proxyMiddleware("/api/all", {
target: "http://localhost:3010",
changeOrigin: true,
}),
],
});

Vite cannot forward the request normally

I'm at vite config. TS configures the request broker, as follows:
// 开发服务器配置
server: {
host: true,
port: VITE_PORT,
open: VITE_OPEN,
proxy: {
"/MockServer": {
// target: "http://172.16.10.215:32340/",
target: "http://172.18.30.123:8510/gdm-analysis/",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/MockServer/, ""), //替换访问api的url适配服务端
},
},
strictPort: false, //接口被占用时尝试使用下个可用端口,
hmr: true,
},
I start the project locally and visit, and the request is correctly forwarded to http://172.18.30.123:8510/gdm-analysis/,but when I use my colleague's computer to access my local project, the request cannot be forwarded normally, header show:Provisional headers are shown

i18next - Loading translations from a JSON file

I'm trying to load translations from a JSON file using i18next library on Node. The path of the JSON file points to the correct location.
I'm getting the following error:
i18next::translator: missingKey en translation test test
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
const instance = i18next.use(Backend).createInstance({
lng: config.language,
debug: true,
fallbackLng: 'en',
initImmediate: false,
backend: {
loadPath: join(__dirname, `${config.language}.json`),
},
}, (error, t) => {
console.log(t('foo'));
});
JSON file:
{
"foo": "bar"
}
Specifying the translations directly in createInstance using resources property works perfectly.
I tried everything I could think of, with no success.
Found the solution!
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
const instance = i18next.use(Backend).createInstance();
instance.init({
lng: config.language,
debug: true,
fallbackLng: 'en',
initImmediate: false,
backend: {
loadPath: join(__dirname, `${config.language}.json`),
},
}, (error, t) => {
console.log(t('foo'));
});

Koa 404 when calling from Vue

I am trying to build an app with Koa and Nuxt. this is what I have:
Define service to retrieve from firestore:
const Firestore = require('#google-cloud/firestore');
const getItems = () => {
const db = new Firestore({
projectId: '*******',
keyFilename: "******"
});
db.collection('items').get()
.then((snapshot) => {
return snapshot;
})
}
Define them in routes.js:
const Router = require('#koa/router');
const articleService = require('./services/itemservice');
const router = new Router();
router.get('/getitems', async(ctx, next) => {
ctx.body = articleService.getItems();
});
module.exports = router;
Add routes to retrieve from routes.js:
app.use(router.routes());
app.use(router.allowedMethods());
And finally call it from a component:
let articles = axios.get('/getitems')
.then(response => {
console.log(response);
})//.....
I am receiving this error:
response:
{ status: 404,
statusText: 'Not Found',
headers:
{ 'content-type': 'text/html; charset=us-ascii',
server: 'Microsoft-HTTPAPI/2.0',
date: 'Fri, 25 Oct 2019 16:08:00 GMT',
connection: 'close',
'content-length': '315' },
config:
{ url: '/getarticles',
method: 'get',
headers: [Object],
transformRequest: [Array],
transformResponse: [Array],
timeout: 0,
adapter: [Function: httpAdapter],
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
data: undefined },
request:
ClientRequest {
_header:
'GET /getitems HTTP/1.1\r\nAccept: application/json, text/plain, */*\r\nUser-Agent: axios/0.19.0\r\nHost: localhost\r\nConnection: close\r\n\r\n',
_onPendingData: [Function: noopPendingOutput],
agent: [Agent],
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/getitems',
_ended: true,
res: [IncomingMessage],
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
_redirectable: [Writable],
[Symbol(isCorked)]: false,
[Symbol(outHeadersKey)]: [Object] },
data:
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">\r\n<HTML><HEAD><TITLE>Not Found</TITLE>\r\n<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>\r\n<BODY><h2>Not Found</h2>\r\n<hr><p>HTTP Error 404. The requested resource is not found.</p>\r\n</BODY></HTML>\r\n' },
isAxiosError: true,
toJSON: [Function] }
Can anyone point me in the right direction?
I've never built an app with Nuxt, but I'll try to help you anyway.
First, I recommend you to read about Promises:
- https://github.com/leonardomso/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch3.md
- https://github.com/leonardomso/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch8.md
(those are two chapters of a good JS book series!)
Second, you can try two things in order to find the bug:
- add a .catch block to your thenables, to check if something went wrong;
- add a dummy route that just logs an 'OK', to make sure the routes are be registered and up to respond.
I hope this will help you!
I had this problem with my nuxt / express.js app:
If you would try to type in your browser yourURL/getitems your nuxt app will try to route you to that page instead of just to show u the data.
First thing to do, how to say, you need to define what url your backend should handle.
You go to your nuxt.config.js and add this line of code:
serverMiddleware: ["~/api/index.js"],
That means you have a folder called api and in that folder you have an index.js file and thats your express.js / koa app.
Now in your index.js where your express.js / koa app is you need to add at the end of the line this peace of code:
module.exports = {
path: "/api",
handler: app
};
If everything works fine your URL should have now a prefix api and you should be able to get the data with localhost:PORT/api/getitems
Now nuxt wont try to route you to your url/api because it knows now that this is your backend
If you could provide me your folder structure of your nuxt app i could help you more.
Here is more information about serverMiddleware
https://nuxtjs.org/api/configuration-servermiddleware
EDIT:
somewhere you have a Folder, lets say ist named server or api
in that Folder there should be a index.js file and your routes, model, Controllers etc.
Lets say you have a Folder called server and in that Server you have index.js that should look something like this
const Koa = require('koa');
const app = new Koa();
Import routes from "./routes.js"
app.use(routes)
//here you define now your backend path
module.exports = {
//you can use any path you want
path: "/backend",
handler: app
};
app.listen(3000);
Now you Need to go to your nuxt.config.js file and Point to that index.js File
serverMiddleware: ["~/server/index.js"]
Now you can Access your data with axios:
axios.get("/backend/getitems").then(data => { console.log(data) })
You will Need to add backend to your axios url because thats the path you defined that your Server will handle.

ExpressJS res.render() error (JSON.stringify can't work on circular reference)

What's wrong here?
res.render('/somepage', {user:req.session.user})
It leads to Converting circular structure to JSON errors, (results in session element that has a circular user reference.)
exports.home = function (req, res) {
var entityFactory = new require('../lib/entity-factory.js').EntityFactory();
entityFactory.get_job_task_lists({
callback : function (err, job_task_lists) {
res.render('home.jade', {
locals:{
title: 'Logged in.',
user:req.session.user, // does not work
job_task_lists:job_task_lists || []
}
});
}
});
};
I added some logging in node_modules/express/node_modules/connect/lib/middleware/session/memory.js
MemoryStore.prototype.set = function(sid, sess, fn){
var self = this;
process.nextTick(function(){
console.log(sess); //this is giving the output listed
self.sessions[sid] = JSON.stringify(sess);
...
This is what I expect the session to look like, in terms of structure:
{ lastAccess: 1330979534026,
cookie:
{ path: '/',
httpOnly: true,
_expires: Tue, 06 Mar 2012 00:32:14 GMT,
originalMaxAge: 14399999 },
user: // this is the object I added to the session
{ id: 1,
username: 'admin',
password: '8e3f8d3a98481a9073d2ab69f93ce73b',
creation_date: Mon, 05 Mar 2012 18:08:55 GMT } }
But here's what I find:
{ lastAccess: 1330979534079, // new session
cookie:
{ path: '/',
httpOnly: true,
_expires: Tue, 06 Mar 2012 00:32:14 GMT,
originalMaxAge: 14399999 },
user: // but here it is again, except now it's a mashup,
// containing members it shouldn't have, like locals,
// and, well, everything but the first 4 properties
{ id: 1,
username: 'admin',
password: '8e3f8d3a98481a9073d2ab69f93ce73b',
creation_date: '2012-03-05T18:08:55.701Z',
locals:
{ title: 'Logged in.',
user: [Circular], //and now it's circular
job_task_lists: [Object] },
title: 'Logged in.',
user: [Circular],
job_task_lists: [ [Object], [Object], [Object], getById: [Function] ],
attempts: [ '/home/dan/development/aqp/views/home.jade' ],
scope: {},
parentView: undefined,
root: '/home/dan/development/aqp/views',
defaultEngine: 'jade',
settings:
{ env: 'development',
hints: true,
views: '/home/dan/development/aqp/views',
'view engine': 'jade' },
app:
{ stack: [Object],
connections: 6,
allowHalfOpen: true,
_handle: [Object],
_events: [Object],
httpAllowHalfOpen: false,
cache: [Object],
settings: [Object],
redirects: {},
isCallbacks: {},
_locals: [Object],
dynamicViewHelpers: {},
errorHandlers: [],
route: '/',
routes: [Object],
router: [Getter],
__usedRouter: true },
partial: [Function],
hint: true,
filename: '/home/dan/development/aqp/views/home.jade',
layout: false,
isPartial: true } }
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
TypeError: Converting circular structure to JSON
at Object.stringify (native)
at Array.0 (/home/dan/development/aqp/node_modules/express/node_modules/connect/lib/middleware/session/memory.js:77:31)
at EventEmitter._tickCallback (node.js:192:40)
See how the user object is nested?
Note that this time I did not send values in explicitly with 'locals' but it ended up in one (thats the source of the circular reference.
It looks like the session is being used to transfer objects to the view.
Here's my only middleware (it only reads from the session):
function requiresAuthentication(req, res, next){
if (req.session.user){
next();
} else {
next(new Error('Unauthorized. Please log in with a valid account.'))
}
}
and the only time I modify the req.session is in this route:
app.post('/home', function (req,res,next) {
var auth = require('./lib/authentication');
auth.authenticate_user(req.body.user, function (user) {
if (user){
req.session.user = user;
console.log('authenticated');
res.redirect(req.body.redir || '/home');
//next();
} else {
console.log('not authenticated');
res.render('logins/new.jade', {title: 'Login Failed', redir:''})
}
});
});
I don't have much else going on in my application yet, as it's still quite young. I know I'm not mangling the session anywhere myself; I checked.
I did some more testing, and it appears this is only an issue when I then try to use the local variable on a page. For instance, here is my view home.jade
div(data-role="page")
div(data-role="header")
a(href='/logout', data-icon='delete', data-ajax="false") Log out
h1= title
a(href='/account', data-icon='info', data-ajax="false") Account
!= partial('user', user)
each jtl in job_task_lists
div(id=jtl.name, class = 'draggable_item', style='border:2px solid black;')
#{jtl.name} - #{jtl.description}
a(data-icon='plus')
div(data-role="footer")
h3 footer
script(src="/javascripts/home.js")
If I comment out the user partial, it renders, else I get this Converting circular structure to JSON issue.
UPDATE
So after hooking up eclipse and the v8 debugger, I have been stepping through the code and I know where the mashup of session and user objects is occurring,
in node_modules/connect/lib/middleware/session/session.js
utils.union ends up mashing the members of the user object into the session, causing the circular reference. I'm just not sure why (admittedly probably my code)
This was a problem with session data being modified in a view.
After much digging, I found that it was a bug in the way partials are handled in 2.5.8. I submitted an issue, and subsequently a patch. (in case anyone needs this info at a future date) as npm is still serving up Express 2.5.8 AFAIK.
Thanks for your help #freakish and #Ryan

Resources