how to make angular2 routes work with express in production mode - express

I have 2 folder: server and src. Following: angular2-express-starter
Here is my REPO
server/app.ts:
import { routerUser } from "./routes/user";
const app: express.Application = express();
/* some stuff */
// api routes
// Set our api routes
app.use("/api/user", routerUser);
if (app.get("env") === "production") {
// in production mode run application from dist folder
console.log("PRODUCTION MODE");
app.use(express.static(path.join(__dirname, "/../client")));
}
/* some stuff */
src/app/app.router.ts
import { Routes } from '#angular/router';
import { PostsComponent } from './posts/posts.component';
export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'user/profile'},
{ path: 'user/profile', component: PostsComponent }
];
And following here
I able to run server and client in separated process with proxy config:
{
"/api/*": {
"target": "http://localhost:4300",
"secure": false
}
}
Like this:
tsc -p ./server/debug
node dist-debug/server/bin/www.js
ng server --host 0.0.0.0 --proxy-config proxy.conf.json
But issue come in production mode: build both server and client into dist folder.
When I access localhost:4300, it redirect to localhost:4300/user/profile automatically, it's fine.
But if I access direct to localhost:4300/user/profile
The page response: {"error":{},"message":"Not Found"}

Related

Heroku Deploy Vite Static App Won't Connect to API with Axios Using a Proxy

I think this problem follows this one on netlify API from proxy not working after deploying on netlify
I am setting up a Vite app and making an axios api request in my app component
getSuggestionList(street, zip, city) {
this.axios.get('/1.0/address/find?country=at&zip=' + zip + '&city=' + city + '&street-address=' + street + '&street-number=&offset=1&limit=100', {
auth: {
username: '7166-631A-5394-4C03-9106-0A93-C433-2613',
password: ''
}
})
Now, for development I configured a proxy in server
in vite.config.js
import { fileURLToPath, URL } from "url";
import { defineConfig } from "vite";
import vue from "#vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"#": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
proxy: {
'/1.0/address': 'http://api.opendata.host/'
}
},
proxy: {
'/1.0/address': {
target: 'http://api.opendata.host/',
changeOrigin: true,
secure: false,
ws: true,
}
}
});
Following this guide
https://rubenr.dev/en/cors-vite-vue/
I understand this does not transfer to production, so I also tried the proxy part. I don't get a response, just like described in the netlify guide.
From Heroku docs
https://github.com/heroku/heroku-buildpack-static
I have the basic setup without proxies (leading to the initial, no-response behavior)
in static.json:
{
"root": "./dist",
"clean_urls": true,
"routes": {
"/**": "index.html"
},
"proxies": {
"/1.0/address": {
"origin": "http://api.opendata.host/"
}
}
}
And when I add proxies I get a 404 not found. When I change up the spelling: no response again, so it seems to be making a connection with the proxies configuration. But why not they way it works locally? Does anyone see my error here or having something for me to try?
I found it: for anyone else having trouble with this kind of deployment on Heroku -- the mountpoint "/1.0/address" in static.json seems to be replaced resulting in the 404 not found.
For production I added a prefix /api in my axios call, something like
var apiPath = '/1.0/address/find?country=at&zip=' + zip + '&city=' + city + '&street-address=' + street + '&street-number=&offset=1&limit=100'
var prodMountpoint = '/api' // /api/
if (import.meta.env.PROD) {
apiPath = prodMountpoint + apiPath
}
This results in a correct api call: "/api" is replaced with the proxied host, if I understand correctly. For development, the vite.config.js does its work as before.

Setup of vue.config.js file to imitate production setup (connect two apps)

I run an R Shiny app on port 3000 which serves my vue.js App like this:
library(shiny)
server <- function(input, output, session) {
histogramData <- reactive({
mtcars
})
observe({
session$sendCustomMessage("histogramData", histogramData())
})
}
ui <- function() {
htmlTemplate("dist/index.html")
}
# Serve the bundle at js/main.js
if (dir.exists("dist/js")) {
addResourcePath("js", "dist/js")
}
# Serve the bundle at js/main.js
if (dir.exists("dist/css")) {
addResourcePath("css", "dist/css")
}
# Serve the bundle at js/main.js
if (dir.exists("dist/img")) {
addResourcePath("img", "dist/img")
}
shinyApp(ui, server)
For development, I would change it like this:
ui <- function() {
htmlTemplate("public/index.html")
}
However, I can not always run the build process just to connect the apps, I want to use the dev server to connect the apps and send data back and forth.
I have setup a vue.config.js with the following configuration to create a connection between the two apps.
const path = require('path')
module.exports = {
publicPath: ".",
devServer: {
port: 4000,
contentBase: path.resolve(__dirname, 'public'),
proxy: {
'/': {
target: 'http://localhost:3000'
},
'/websocket': {
target: 'ws://localhost:3000',
ws: true
}
}
},
transpileDependencies: [
"vuetify"
]
}
This was taken from a github repository, I am acutally quite clueless how to archieve this connection. My idea was to connect go on localhost:4000 and receive the data from localhost:3000, but nothing gets passed:
TypeError: Cannot read property 'addCustomMessageHandler' of undefined at VueComponent.mounted (HelloWorld.vue?140d:42)
This is based on the following method in my vue component (which works perfectly after the build process):
mounted: function () {
window.Shiny.addCustomMessageHandler('histogramData', histogramData =>
this.data.histogramData = histogramData
)
Can anyone tell me what´s wrong and help me to setup the connection correctly?

Page refresh or direct load shows blank screen

I've read a number of solutions to this same problem, but none have worked for me. So here it goes.
I have a Vue 2 app using Express that runs on AWS Amplify. When I run my app locally in 'dev' mode (npm run serve) and 'start' mode (npm run build && node server.js), everything works fine.
The problem shows up when I deploy to Amplify. I can click nav buttons, go back, and go forward, all of which send me to the correct URL. However, the moment I refresh the page or manually enter a valid URL in the browser, the screen goes blank and the browser URL shows https://dontblowup.co/index.html.
Below is my server.js file:
const express = require('express')
const path = require('path')
const history = require('connect-history-api-fallback')
const app = express()
const port = process.env.PORT || 8080
const buildLocation = 'dist'
const staticFileMiddleware = express.static(path.resolve(__dirname, buildLocation))
app.use(staticFileMiddleware)
app.use(history())
app.use(staticFileMiddleware)
app.get('*', function (req, res) {
res.sendFile(path.resolve(__dirname, buildLocation, 'index.html'))
})
app.listen(port, () => {
console.log(`App listening to port ${port}...`)
console.log('Press Ctrl+C to quit.')
})
The solutions I found included using the history package in the server.js file and using the staticFileMiddleware before and after using history(). I also have 'history' mode set in the Vue app's router.js file (see below).
import Vue from "vue";
import VueRouter from "vue-router";
import Index from "./views/Index.vue";
import MainFooter from "./layout/MainFooter.vue";
import DashboardLayout from "#/layout/DashboardLayout.vue";
import Analytics from "#/views/Analytics.vue";
import TradeSheet from "#/views/TradeSheet.vue";
import KellyCriterionCalculator from "#/views/KellyCriterionCalculator.vue";
import PositionBuilder from "#/views/PositionBuilder.vue";
Vue.use(VueRouter);
export default new VueRouter({
mode: 'history',
routes: [
{
path: "/",
name: "Index",
components: { default: Index, footer: MainFooter },
props: {
footer: { backgroundColor: "black" }
},
meta: { requiresAuth: false }
},
{
path: "/dashboard",
redirect: "/dashboard/analytics",
name: "Dashboard",
component: DashboardLayout,
meta: { requiresAuth: true },
children: [
{
path: "/dashboard/analytics",
name: "Analytics",
component: Analytics
},
{
path: "/dashboard/trade-sheet",
name: "Trade Sheet",
component: TradeSheet
},
{
path: "/dashboard/risk-budget-calculator",
name: "Risk Budget Calculator",
component: KellyCriterionCalculator
},
{
path: "/dashboard/trade-analyzer",
name: "Trade Analyzer",
component: PositionBuilder
}
]
}
],
scrollBehavior: to => {
if (to.hash) {
return { selector: to.hash };
} else {
return { x: 0, y: 0 };
}
}
});
At this point I'm convinced there's something wrong with Amplify or Namecheap (where my DNS is configured). Unfortunately I haven't found anyone with the same issues using the same tech, so hopefully someone here can help.
Cheers!
You need to set up it on the Amplify Console
Navigate to the Amplify Console
On the left menu click on "Rewrites and redirects"
Click on Edit
Add the rule:
Source: </^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/>
Target: /
Type: 200
You can read more about it here
Go to section: Redirects for Single Page Web Apps (SPA)
Most SPA frameworks support HTML5 history.pushState() to change browser location without triggering a server request. This works for users who begin their journey from the root (or /index.html), but fails for users who navigate directly to any other page. Using regular expressions, the following example sets up a 200 rewrite for all files to index.html except for the specific file extensions specified in the regular expression.
the better way to handle SPA call to index.html will be
app.get(/.*/, (req, res) => res.sendFile(__dirname + '/dist/index.html'))
in frontend vue router you need to add a redirect route like this
{
path: "*",
redirect: "/"
}

vuejs routing with express server

I've a node.js (express) application listening at localhost:4000/api
I'm working on a vuejs based client with router as follows,
const routes = [
{
path: '/customers',
name: 'customers',
component: Customers,
},
{
path: '/customers/:customer_id',
name: 'customer_details',
component: CustomerDetails,
},
]
I'm managing server path via vue.config.js file as,
const path = require('path');
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000'
}
}
}
}
I'm also using state mgmt via store object/functionality as follows,
const BASE_URL = 'api/'
fetchCustomers(context) {
const server_url = BASE_URL + '/customers'
axios.get(server_url)
...
}
This works fine, but
fetchCustomer(context, id) {
const server_url = BASE_URL + '/customers/' + id
axios.get(server_url)
...
}
This one fails with 404 error. When I checked on the server, I see that client is making call to,
/customers/api//customers/5c891995e5212d439459ff28
instead of
/api//customers/5c891995e5212d439459ff28
And at the client side I've a router-link as follows,
<router-link :to="{'name':'customer_details',
params: {'customer_id': cid }}></router-link>
When I click on this link, I'm seeing following in the console,
GET http://localhost:8080/customers/api//customers/5c891995e5212d439459ff28 404 (Not Found)
If I remove vue.config.js and put BASE_URL with complete path as 'http://localhost:4000/api', then I don't see this error. Must be very small mistake but I'm not seeing it. Any help is appreciated.

Programmatically bundling Nuxt.js application

I am newish to Nuxt world so I will try to describe what I need and what I was failing to do.
I am trying to programmatically build Nuxt application, bundle it and to mount it to a route
const { Nuxt, Builder } = require('nuxt');
const options = require('./nuxt.config.js');
const nuxt = new Nuxt(options);
try {
await new Builder(nuxt).build();
} catch(error) {
logger.error('Error building');
logger.log({ level: 'error', message: error });
}
So what I am interested in is programmatically controlling on how my Nuxt application will be bundled. That should not be an issue since my app is aware of their environment during build time.
So for production environment I would like to load everything bundled and minified/uglified and what else... So if possible I would like to load all html stuff + 1 JS file + 1 css file.
my example config file is
module.exports = {
build: {
// I should put something here
}
},
srcDir: 'app/view/',
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
head: {
script: [
{ rel: 'preload', src: `https://connect.facebook.net/en_US/sdk.js#xfbml=1&version=v3.2&appId=${facebookAppId}&autoLogAppEvents=1` }
],
meta: [
]
},
axios: {
port: 3010
},
router: {
middleware: [ /*my middlewares*/ ]
}
};
So the question is how can I control build in order to achieve what I want? Bonus point for me would be if I managed to load scripts from head.script and merge it to bundle.js file