NuxtJs serverMiddleware causing error on production - express

The NuxtJs app running well with npm run dev but npm run build causing the following error
Here is my server/index.js
require('dotenv').config()
const express = require('express')
const { Nuxt, Builder } = require('nuxt')
const app = express()
const api = require('./api')
const socket = require('./socket')
const host = process.env.HOST
const port = process.env.PORT
app.set('port', port)
// Listen the server
const server = app.listen(port, host)
const io = require('socket.io').listen(server)
// Import and Set Nuxt.js options
let config = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
async function start() {
// Init Nuxt.js
const nuxt = new Nuxt(config)
// 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)
console.log('Server listening on http://' + host + ':' + port) // eslint-disable-line no-console
}
start()
// Online users list by country
app.locals.onlineUsers = {};
// Users who is searching partners
app.locals.searchingUsers = {}
socket.start(io, app)
app.use('/api', api)
module.exports = app
And here is my nuxt.config.js
require('dotenv').config()
const pkg = require('./package')
module.exports = {
mode: 'universal',
debug: true,
serverMiddleware: [
{ path: '/api', handler: '~/server/index.js' }
],
/*
** Headers of the page
*/
head: {
title: pkg.name,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: 'http://chateg.com/favicon.ico' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css?family=Fredoka+One|Montserrat:200,400,500,700,800&subset=cyrillic' }
]
},
/*
** Customize the progress-bar color
*/
loading: false,
/*
** Global CSS
*/
css: [
'#/assets/scss/main.scss'
],
/*
** Router middlewares
*/
router: {
middleware: 'i18n'
},
/*
** Plugins to load before mounting the App
*/
plugins: [
{ src: '~/plugins/i18n.js', ssr: true },
{ src: '~/plugins/vue-select.js', ssr: false },
{ src: '~/plugins/vue-flux.js', ssr: false },
{ src: '~plugins/ga.js', ssr: false },
{ src: '~plugins/socket.io.js', ssr: false }
],
/*
** Generate dynamic routes
*/
generate: {
routes: [
'/ru/login',
'/uz/login',
'/ru/chat',
'/uz/chat'
]
},
/*
** Nuxt.js modules
*/
modules: [
'#nuxtjs/axios',
'#nuxtjs/router',
'#nuxtjs/sitemap',
['#nuxtjs/component-cache', {
max: 10000,
maxAge: 1000 * 60 * 60
}],
['#nuxtjs/google-analytics', {
id: 'UA-129371850-1'
}]
],
/*
** Generates sitemap
*/
sitemap: {
path: '/sitemap.xml',
hostname: 'http://chateg.com',
cacheTime: 1000 * 60 * 15,
gzip: true,
generate: false, // Enable me when using nuxt generate
exclude: [
'/secret',
'/admin/**'
],
routes: [
'/ru/login',
'/uz/login',
'/ru/chat',
'/uz/chat'
]
},
/*
** Axios module configuration
*/
axios: {
baseURL: process.env.BASE_URL
},
/*
** Build configuration
*/
build: {
// analyze: true,
/*
** You can extend webpack config here
*/
extend(config, ctx) {
// config.resolve.alias['vue'] = 'vue/dist/vue.common'
// Run ESLint on save
if (ctx.isDev && ctx.isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
}
}
Here is my server/api/index.js
const express = require('express')
const router = express.Router()
const request = require('request')
const getPostData = require('../middleware/getPostData')
const help = require('../helper')
router.use(getPostData)
router.post('/login', function (req, res) {
let user = null;
if (req.body.user) {
user = req.body.user;
console.log(user)
}
if (!user.sex || !user.partner || !user.country || !user.token) {
return res.status(400).json({ message: "Something went wrong!" });
}
const verifyCaptchaOptions = {
uri: "https://www.google.com/recaptcha/api/siteverify",
json: true,
form: {
secret: '6Ld4RnQUAAAAAPdZaBbHdginQWteAohILLt1OXuT',
response: user.token
}
}
request.post(verifyCaptchaOptions, function (err, response, body) {
if (err) {
return res.status(500).json({ message: "oops, something went wrong on our side" });
}
if (!body.success) {
return res.status(500).json({ message: body["error-codes"].join(".") });
}
//Save the user to the database. At this point they have been verified.
res.status(201).json({ success: true, user: user });
});
})
router.get('/get_users', function (req, res) {
let numberOfOnlineUsers = 0;
let users = req.app.locals.onlineUsers;
if (Object.keys(users).length) {
for (const key in users) {
if (users.hasOwnProperty(key)) {
numberOfOnlineUsers += help.countArray(users[key]);
}
}
}
res.status(201).json({ onlineUsers: numberOfOnlineUsers })
})
router.get('/get_users_from_country', function (req, res) {
let numberOfOnlineUsers = 0;
let countryId = 'undefined';
let users = req.app.locals.onlineUsers;
if (req.query.countryId) {
countryId = req.query.countryId;
}
if (users.hasOwnProperty(countryId) && users[countryId].length > 0) {
numberOfOnlineUsers = help.countArray(users[countryId]);
}
res.status(201).json({ onlineUsers: numberOfOnlineUsers })
})
router.get('/get_males_and_females', function (req, res) {
let males = 0;
let females = 0;
let countryId = 'undefined';
let users = req.app.locals.onlineUsers;
if (req.query.countryId) {
countryId = req.query.countryId;
}
if (users.hasOwnProperty(countryId) && users[countryId].length > 0) {
users[countryId].forEach(item => {
if (item.sex == 'female') {
females++;
} else if (item.sex == 'male') {
males++;
}
})
}
res.status(201).json({ males: males, females: females })
})
module.exports = router
I am sure the bug related to serverMiddleware of nuxt in the config cause when I commented it it is working good but I can't access my API.
Please help me as much as possible. I have not been able to solve this problem for 2 days.

Related

(vite or rollup) build exoprt name will be change

I want to try to use vite to package my component library, but I encounter a problem. After packaging, I will change the name of the variable exported from the file
this is vite.config.js:
import { defineConfig } from "vite";
import path from "path";
import { createVuePlugin } from "vite-plugin-vue2";
import pkg from "./package.json";
const inspector = require("inspector");
inspector.open("9292", "127.0.0.1");
const extensions = [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"];
export default (/** if you want to use mode : { mode }*/) =>
defineConfig({
mode: "production",
base: "./",
plugins: [createVuePlugin()],
resolve: {
extensions,
alias: [
{
find: `#gb/utils`,
replacement: path.resolve(__dirname, "../../packages/utils/src"),
},
],
},
css: {
modules: {
scopeBehaviour: "global",
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
charset: false,
},
scss: {
charset: false,
},
},
},
build: {
lib: {
entry: path.resolve(__dirname, "src/index.js"),
name: "index",
formats: ["es"],
fileName: () => `index.js`,
},
cssCodeSplit: true,
outDir: "es",
assetsDir: "style",
watch: {},
minify: "minify",
rollupOptions: {
output: {
// preserveModules: true,
// preserveModulesRoot: "src",
// assetFileNames: ({ name }) => {
// const { ext, dir, base } = path.parse(name);
// console.log(ext, dir, base);
// if (ext !== ".css") return "[name].[ext]";
// return path.join(dir, "style", 'style.css');
// },
chunkFileNames: (chunkInfo) => {
// console.log(chunkInfo);
return "[name].js";
},
manualChunks: (id, { getModuleInfo, getModuleIds }) => {
const filePath = path.parse(id);
const dirPath = filePath.dir
.replace(/.*\/src/, "")
.replace("/", "");
if ([".css", ".scss", ".less"].includes(filePath.ext)) {
return `${dirPath}/style`;
}
if (/vue&type=template&lang/.test(filePath.name)) {
const realName = filePath.name.replace(
/\.vue\?vue\&type\=template\&lang/,
""
);
return `${dirPath}/${realName}`;
}
if (dirPath) {
return `${dirPath}/${filePath.name}`;
}
return `${filePath.name}`;
},
},
external: [
...Object.keys(pkg.devDependencies),
/#gb\/utils.*/,
/node_modules/,
],
},
},
});
this is my code:
export default function (fileUrl = '') {
let file = fileUrl.split('?')[0]
let fileName = file.substring(file.lastIndexOf('/') + 1) || ''
let suffixLastIndex = fileName.lastIndexOf('.')
let name = fileName.substring(0, suffixLastIndex)
let suffix = fileName.substring(suffixLastIndex + 1, fileName.length)
return {
name,
suffix
}
}
when i build, the export name will be a,b,c...z:
function getUrlToFileSuffix(fileUrl = "") {
let file = fileUrl.split("?")[0];
let fileName = file.substring(file.lastIndexOf("/") + 1) || "";
let suffixLastIndex = fileName.lastIndexOf(".");
let name = fileName.substring(0, suffixLastIndex);
let suffix = fileName.substring(suffixLastIndex + 1, fileName.length);
return {
name,
suffix
};
}
export { getUrlToFileSuffix as g };
I need correct name
some people know how to set config?
Thanks so much!
set output.minifyInternalExports = false

Nuxt.js Oauth sometimes crashes whole webpage

I have created Nuxt.js application, I decided to build in Nuxt/auth module, everything works fine in web browsers, but somethimes when user navigates with mobile browser my application is crushed, simply it don't respond nothing, also there is no api calls, but after one refresh everything works fine, I can not guess what's happening, I could not find anything in the resources available on the Internet.
const axios = require('axios')
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'app',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script: [
// { src: "//code-eu1.jivosite.com/widget/UoBOrMfSmm", async: true },
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [ '~/assets/css/transition.css', '~/assets/css/errors.css' ],
pageTransition: "fade",
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
{ src: "~/plugins/star-rating", ssr: false },
{ src: "~/plugins/mask", ssr: false },
{ src: "~/plugins/rangeSlider", ssr: false },
{ src: "~/plugins/vueSelect", ssr: false },
{ src: "~/plugins/vuelidate", ssr: false },
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
[ '#nuxtjs/google-analytics', {
id: 'xxx'
} ]
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'#nuxtjs/axios',
'#nuxtjs/toast',
'#nuxtjs/auth-next',
[ 'nuxt-lazy-load', {
defaultImage: '/spin2.gif'
} ],
[ 'nuxt-facebook-pixel-module', {
/* module options */
track: 'PageView',
pixelId: '',
autoPageView: true,
disabled: false
} ],
'nuxt-moment',
'#nuxtjs/robots',
'#nuxtjs/sitemap'
],
moment: {
locales: ['ru', 'en']
},
toast: {
position: 'top-center',
},
robots: [
{
UserAgent: '*',
Disallow: ['/user', '/admin'],
},
],
axios: {
baseURL: 'https://api.test.com/', // Used as fallback if no runtime config is provided
},
sitemap:{
exclude:[
'/user',
'/admin',
'/admin/*',
'/user/*',
],
defaults: {
changefreq: 'daily',
priority: 1,
lastmod: new Date()
},
routes: async () => {
const { data } = await axios.get('https://api.test.com/api/cars/all')
return data.map((product) => `https://test.com/product/${product.id}/${product.name}`)
}
},
loading: {
color: '#F48245',
height: '4px'
},
target: 'server',
/* auth */
auth: {
plugins:[
{ src: "~/plugins/providers", ssr:false},
],
redirect: {
login: '/',
logout: '/',
home: '/',
callback: '/callback'
},
strategies: {
local: {
token: {
property: 'user.token',
},
user: {
property: false
},
endpoints: {
login: { url: 'api/login', method: 'post' },
logout: { url: 'api/logout', method: 'post' },
user: { url: 'api/user', method: 'get' }
},
},
facebook: {
endpoints: {
userInfo: 'https://graph.facebook.com/v6.0/me?fields=id,name,picture{url}',
},
redirectUri:'xxx',
clientId: '184551510189971',
scope: ['public_profile', 'email'],
},
google: {
responseType: 'token id_token',
codeChallengeMethod: '',
clientId: 'xxx',
redirectUri: 'https://test.com/callback',
scope: ['email'],
},
},
cookie: {
prefix: 'auth.',
},
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {},
};
This is my plugins directory file, where i am handling client oauth process.
export default async function ({ app }) {
console.log('auth executed')
if (!app.$auth.loggedIn) {
return
} else {
console.log('auth executed inside loop')
const auth = app.$auth;
const authStrategy = auth.strategy.name;
if (authStrategy === 'facebook') {
let data2 = {
fb_token: auth.user.id,
first_name: auth.user.name
}
try {
const response = await app.$axios.$post("/api/oauth", data2);
await auth.setStrategy('local');
await auth.strategy.token.set("Bearer " + response.user.token);
await auth.fetchUser();
} catch (e) {
console.log(e);
}
} else if (authStrategy === 'google') {
let dataGoogle = {
google_token: auth.user.sub,
first_name: auth.user.given_name,
last_name:auth.user.family_name
}
try {
const response = await app.$axios.$post("/api/oauth", dataGoogle);
await auth.setStrategy('local');
await auth.strategy.token.set("Bearer " + response.user.token);
await auth.fetchUser();
} catch (e) {
console.log(e);
}
}
}
}
For any issues related to DOM hydration, you can check my answer here: https://stackoverflow.com/a/67978474/8816585
It does have several possible cases (dynamic content with a difference between client side and server side rendered template, some random functions, purely wrong HTML structure etc...) and also a good blog article from Alex!

How to remove console logs from Production Build in Vue 3

Here is my vue.config.js file. I have followed the instructions given here: https://github.com/JayeshLab/demo-vue-console-log-removal
Still I can see logs in browser, what else am I missing?
const TerserPlugin = require("terser-webpack-plugin");
const isProd = true
module.exports = {
lintOnSave: false,
devServer: {
proxy: 'http://localhost:3000/'
},
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(args => {
})
},
configureWebpack: {
optimization: {
minimize: true,
minimizer: isProd ? [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true
},
output: {
comments: false
}
}
})
] : []
}
}
}
Managed to solve it by changing babel.config.js configuration. Added transform-remove-console plugin
const removeConsolePlugin = []
if(process.env.NODE_ENV == 'staging' || process.env.NODE_ENV == 'production') {
removeConsolePlugin.push("transform-remove-console")
}
module.exports = {
presets: [
'#vue/cli-plugin-babel/preset'
],
plugins: removeConsolePlugin
}

Proxy `changeOrigin` setting doesn't seem to work

I'm using Vue CLI 3.0.0 (rc.10) and am running two servers (backend server and WDS) side by side.
I followed the devServer.proxy instructions on the Vue CLI documentation to add a proxy option to my vue.config.js. I also followed the instructions for the http-proxy-middleware library to supplement the options:
module.exports = {
lintOnSave: true,
outputDir: '../priv/static/',
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000',
changeOrigin: true,
ws: true,
},
},
},
};
My understanding is that the changeOrigin: true option needs to dynamically change the Origin header on the request to "http://localhost:4000". However, requests from my app are still being sent from http://localhost:8080 and they trigger CORS blockage:
Request URL: http://localhost:4000/api/form
Request Method: OPTIONS
Status Code: 404 Not Found
Remote Address: 127.0.0.1:4000
Host: localhost:4000
Origin: http://localhost:8080 <-- PROBLEM
What am I doing wrong?
I was having basically the same problem, and what finally worked for me was manually overwriting the Origin header, like this:
module.exports = {
lintOnSave: true,
outputDir: '../priv/static/',
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000',
changeOrigin: true,
ws: true,
onProxyReq: function(request) {
request.setHeader("origin", "http://localhost:4000");
},
},
},
},
};
this is my vue.config.js, work fine for me:
module.exports = {
baseUrl: './',
assetsDir: 'static',
productionSourceMap: false,
configureWebpack: {
devServer: {
headers: { "Access-Control-Allow-Origin": "*" }
}
},
devServer: {
proxy: {
'/api': {
target: 'http://127.0.0.1:3333/api/',
changeOrigin: false,
secure: false,
pathRewrite: {
'^/api': ''
},
onProxyReq: function (request) {
request.setHeader("origin", "http://127.0.0.1:3333");
}
}
}
}
}
axios.config.js:
import axios from 'axios';
import { Message, Loading } from 'element-ui'
// axios.defaults.baseURL = "http://127.0.0.1:3333/";
axios.defaults.timeout = 5 * 1000
// axios.defaults.withCredentials = true
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
let loading = null;
function startLoading() {
loading = Loading.service({
lock: true,
text: 'loading....',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.8)'
})
}
function endLoading() {
loading.close()
}
axios.interceptors.request.use(
(confing) => {
startLoading()
// if (localStorage.eToken) {
// confing.headers.Authorization = localStorage.eToken
// }
return confing
},
(error) => {
console.log("request error: ", error)
return Promise.reject(error)
}
)
axios.interceptors.response.use(
(response) => {
endLoading()
return response.data;
},
(error) => {
console.log("response error: ", error);
endLoading()
return Promise.reject(error)
}
)
export const postRequest = (url, params) => {
return axios({
method: 'post',
url: url,
data: params,
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
}
export const uploadFileRequest = (url, params) => {
return axios({
method: 'post',
url: url,
data: params,
headers: {
'Content-Type': 'multipart/form-data'
}
});
}
export const getRequest = (url) => {
return axios({
method: 'get',
url: url
});
}
export const putRequest = (url, params) => {
return axios({
method: 'put',
url: url,
data: params,
transformRequest: [function (data) {
let ret = ''
for (let it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
}
export const deleteRequest = (url) => {
return axios({
method: 'delete',
url: url
});
}
api request:
import {postRequest} from "../http";
const category = {
store(params) {
return postRequest("/api/admin/category", params);
}
}
export default category;
the principle:
According to https://github.com/chimurai/http-proxy-middleware#http-proxy-options, a header option works for me.
devServer: {
proxy: {
'/api': {
target: 'http://127.0.0.1:3333/api/',
headers: {
origin: "http://127.0.0.1:3333"
}
}
}
}
changeOrigin only changes the host header!!!
see the documents http-proxy-middleware#http-proxy-options
option.changeOrigin: true/false, Default: false - changes the origin of the host header to the target URL

webpack-dev-server does not properly proxy redirect to the backend with hapi and and hapi-auth-cookie

When i boot up the hapi-server as well as the webpack-dev-server and go to localhost:3000/api/login shows a 502 bad gateway and nothing on the page! Thanks for every one who helps
Here is my webpack file:
module.exports = {
entry: ["./index.js"],
output: {
filename: "bundle.js"
},
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015']
}
}
]
},
resolve: {
extensions: ['', '.js', '.jsx']
},
devServer: {
historyApiFallback: {
index: 'index.html'
},
stats: 'normal',
host: process.env.HOST || 'localhost',
port: process.env.PORT || 3000,
proxy: {
'/api/*': {
target: 'http//localhost:9000',
secure: false
}
}
}
};
Here is my hapi-server:
const Hapi = require('hapi');
const Inert = require('inert');
const config = require('./config');
const CookieAuth = require('hapi-auth-cookie');
const server = new Hapi.Server();
server.connection({port: 9000});
const options = {
ops: {
interval: config.hapiGoodInterval
},
reporters: {
console: [{
module: 'good-squeeze',
name: 'Squeeze',
args: [{ log: '*', response: '*' }]
}, {
module: 'good-console'
}, 'stdout'],
file: [{
module: 'good-squeeze',
name: 'Squeeze',
args: [{ ops: '*' }]
}, {
module: 'good-squeeze',
name: 'SafeJson'
}, {
module: 'good-file',
args: ['./logs/fixtures/file_log']
}]
}
};
server.register(CookieAuth, (err) => {
if (err) {
throw err;
}
});
server.register(Inert, ()=>{});
server.register({
register: require('good'),
options,
}, (err) => {
if (err) {
return console.error(err);
}
server.start(() => {
console.info(`Server started at ${ server.info.uri }`);
});
});
const cache = server.cache({ segment: 'sessions', expiresIn: 3 * 24 * 60 * 60 * 1000 });
server.app.cache = cache;
server.auth.strategy('session', 'cookie', true, {
password: 'Vaj57zED9nsYeMJGP2hnfaxU874t6DV5',
cookie: 'sid-example',
redirectTo: '/api/login',
isSecure: false,
validateFunc: function (request, session, callback) {
cache.get(session.sid, (err, cached) => {
if (err) {
return callback(err, false);
}
if (!cached) {
return callback(null, false);
}
return callback(null, true, cached.account);
});
}
});
server.route(require('./routes'));
Here are the routes:
var Handlers = require('./handlers');
var Joi = require('joi');
var Routes = [
{
path: '/api/hello',
method: 'GET',
config: {
auth:false,
handler: function (request, reply) {
reply('Hello from the server')
}
}
},
{
method: ['GET', 'POST'],
path: '/api/login',
config: {
handler: Handlers.login,
auth: { mode: 'try' },
plugins: {
'hapi-auth-cookie': { redirectTo: false }
}
}
},
{
path: '/api/logout',
method: 'GET',
handler: Handlers.logout
}
];
And Finally the handlers:
const r = require('rethinkdb');
var {Post, User, Opinion} = require('./rethinkdb/models/all');
class Handlers {
static login(request, reply) {
let username = 'pesho';
let password = '12345';
let uuid = 1;
if (request.auth.isAuthenticated) {
return reply.redirect('/');
}
let message = '';
let account = null;
if (request.method === 'post') {
if (!request.payload.username ||
!request.payload.password) {
message = 'Missing username or password';
}
else {
if (password !== request.payload.password || username !== request.payload.username) {
message = 'Invalid username or password';
}
}
}
if (request.method === 'get' || message) {
return reply('<html><head><title>Login page</title></head><body>' +
(message ? '<h3>' + message + '</h3><br/>' : '') +
'<form method="post" action="/api/login">' +
'Username: <input type="text" name="username"><br>' +
'Password: <input type="password" name="password"><br/>' +
'<input type="submit" value="Login"></form></body></html>');
}
const sid = String(++uuid);
request.server.app.cache.set(sid, { account: account }, 0, (err) => {
if (err) {
reply(err);
}
request.cookieAuth.set({ sid: sid });
return reply.redirect('/');
});
}
static logout(request, reply) {
request.cookieAuth.clear();
return reply.redirect('/');
};
}
module.exports = Handlers;
The problem was kind-a dump, but here it is:
you must add a "/" at the end of the proxy target
proxy: {
'/api/*': {
target: 'http://localhost:9000/',<= This here
secure: false
}
},