Proxy to backend with default Next.js dev server - javascript

Before, when I made apps with create-react-app, I would have a setupProxy.js file that would route API requests similar to this
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use('/api',
proxy({
target: 'http://localhost:8000',
changeOrigin: true,
})
);
};
But that doesn't seem to work with next.js. When I do the same thing, I get various errors.
Googling a solution, a lot say to use a custom server of some kind. But how would I accomplish a proxy like above using the default nextjs dev server? (Equivalent of npm run dev when dev in my package.json is next dev.

There is now an official feature for this in the config: Rewrites
Besides normal path rewrites, they can also proxy requests to another webserver
next.config.js:
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:8000/:path*' // Proxy to Backend
}
]
}
}
See Next.js Docs Rewrites

My server.js set up, hope it helps:
import express from 'express';
import next from 'next';
import proxy from 'http-proxy-middleware';
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = express();
server.use(
'/api',
proxy({
target: process.env.API_HOST,
changeOrigin: true,
}),
);
server.all('*', (req, res) => handle(req, res));
server.listen(port, err => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
});
package.json:
"scripts": {
"dev": "NODE_ENV=development node -r esm server.js",
"build": "NODE_ENV=production next build",
"start": "NODE_ENV=production node -r esm server.js",
},

Another solution with catch-all routes + http-proxy-middleware:
// pages/api/proxy/[...slug].js
import { createProxyMiddleware } from "http-proxy-middleware"; // #2.0.6
const proxy = createProxyMiddleware({
target: process.env.BACKEND_URL,
secure: false,
pathRewrite: { "^/api/proxy": "" }, // remove `/api/proxy` prefix
});
export default function handler(req, res) {
proxy(req, res, (err) => {
if (err) {
throw err;
}
throw new Error(
`Request '${req.url}' is not proxied! We should never reach here!`
);
});
}
see: https://stackoverflow.com/a/72310680

Rewrites didn't work for me, and neither did using axios config.proxy.
I've resorted to a good old constant:
const SERVER_URL =
process.env.NODE_ENV === 'production' ? 'https://produrl.com : 'http://localhost:8000';
export async function getStaticProps() {
const data = axios.get(`${SERVER_URL}/api/my-route`)
// ...
}
I would much rather proxy requests and keep my requests cleaner, but I don't have a day to spend wrestling with this.
Maybe this very simple setup will help others.

Related

React + Nodemailer + Express contact form: How do I get it working in Production?

I'm building a React website that features a simple contact form powered by Nodemailer and an Express backend. The folder structure of my project is as follows:
The form works as expected locally but not in Production. I figured some of my initial configuration would need to be updated, but it's been difficult to find a single source of truth - lots of tutorials on how to get such a form working locally, but minimal guidance beyond that.
In particular, these are the pieces that I know are going to need to be updated:
client/package.json: I've defined "proxy": "http://127.0.0.1:8080/"
client/src/components/Contact/Contact.js: In my submitRequest handler, I've also specified a localhost address in my axios post request
axios.post('http://localhost:8080/api', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
data
})
server/index.js
const express = require('express');
const path = require('path');
const cors = require('cors');
const nodemailer = require('nodemailer');
const dotenv = require('dotenv');
let app = express();
dotenv.config();
app.use(express.json());
app.options('*', cors());
app.get('/api', (req, res) => {
res.send('Welcome to the backend!')
})
app.use('/public', express.static(path.join(__dirname, 'public')));
let transporter = nodemailer.createTransport({
host: process.env.MAIL_HOST,
name: process.env.MAIL_HOST,
port: 465,
secure: true,
auth: {
user: process.env.MAIL_USER,
pass: process.env.MAIL_PASS
},
tls: { rejectUnauthorized: false },
logger: true
});
transporter.verify((error, success) => {
console.log(error ? error : 'Server is ready to take our messages');
});
app.post('/api', cors(), (req, res) => {
const name = req.body.data.name,
email = req.body.data.email,
message = req.body.data.message,
mail = {
from: name + ' <' + email + '>',
to: process.env.MAIL_TO,
subject: 'Contact Form Submission',
text: message
};
transporter.sendMail(mail, (err, data) => {
res.json({ status: err ? 'fail' : 'success' });
});
});
const PORT = process.env.PORT || 8080
app.listen(PORT, () => console.info(`server has started on ${PORT}`))
package.json (for server): an additional build will have to be added to scripts, one that links both client/ and server/. As of now, this is all I have:
"scripts": {
"server": "node server/index.js",
"client": "cd client && npm start",
"build:client": "cd client && npm run build",
"iall": "npm install && cd client && npm install",
"startboth": "npm run server && npm run client"
}
What research I've done seems to suggest I need to use Heroku or a similar service to run the backend in Production, but surely there's a way to have a functional contact form that doesn't require me to pay some kind of monthly fee (be it a PaaS like Heroku, or more streamlined solutions like SendGrid and EmailJS)?? If you can't already tell, backend isn't my forte. And so I would very much appreciate some guidance to get this form working, both locally and in Production.
FYI, the Production site can be found here (don't judge, this website is very much still in the skeleton phase). I can also provide a link to the Github repo if that would be helpful.

Unable to get graphql response from server that's running on local

I have a mongodb server setup which on running the below command starts on port 3000
npm run start
I also a graphql server which on running the below command starts at port 4000
npm run start-graphql
the scripts of my package.json is as below
"scripts": {
"start": "nodemon server.js",
"start-graphql": "nodemon graphqlserver.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
server.js
require('dotenv').config();
const express = require('express');
const app = express();
const mongoose = require('mongoose');
mongoose.connect(process.env.DATABASE_URL);
const db = mongoose.connection;
db.on('error', (err) => console.log(err));
db.once('open', () => {
console.log("Backend Database connected");
});
app.use(express.json({ limit: '2mb'}));
const photosRouter = require('./routes/photos');
app.use('/images', photosRouter)
app.listen(3000, () => {
console.log('Server started at port 3000');
})
graphqlserver.js
const express = require('express');
const path = require('path');
const express_graphql = require('express-graphql').graphqlHTTP;
const { loadSchemaSync } = require('#graphql-tools/load');
const { GraphQLFileLoader } = require('#graphql-tools/graphql-file-loader');
const { addResolversToSchema } = require('#graphql-tools/schema');
const getResolvers = require('./graphql/resolvers');
// GraphQL schema
const combinedSchema = loadSchemaSync(
path.join(__dirname, './graphql/schemas/*.graphql'),
{
loaders: [new GraphQLFileLoader()],
}
);
const schema = addResolversToSchema({
schema: combinedSchema,
resolvers: Object.assign({}, getResolvers())
});
// Create an express server and a GraphQL endpoint
const app = express();
app.use('/graphql', express_graphql({
schema: schema,
graphiql: true
}));
app.listen(4000, () => console.log('Express GraphQL Server Now Running On localhost:4000/graphql'));
when I call the rest api's normally either through postman or curl it returns the response as expected.
For eg: http://localhost:3000/images returns me an array of objects
But When I want to call (using axios) the same via the graphql server (which is running on port 4000 ),
I get response as null.
I have no clue why this is happening.
Please check the below screenshot for your reference
Note: For better clarity please check the codebase link
https://github.com/yaswankar/G-photos-backend
Any help would be appreciated.
Thanks in advance
Request: Please help by giving an upvote so that it could better reach to those who could help.
Edit:
New Error screenshot
I was able to resolve the main issue by adding the query block inside photos resolver
Query: {
photo: photosContext,
},
The other error was resolved by processing the response instead of sending the raw data to the hyper class
async function getActivePhotos(parent, args, req) {
try {
const activePhotos = await photoService.getActivePhotos(req).then(resp => resp.data.map(item => item)); // Process and mapping data
return activePhotos;
} catch (error) {
// logger.error(__filename + ': Failed to get response for getActivePhotos, err=' + JSON.stringify(error));
return new GraphQLError(JSON.stringify(error));
}
}

Next.js Express Custom Server { dev: true } loads forever (empty response) but production { dev: false } works

I am using a express custom server with next.js. Custom Server
I added the next app to my router under the path /custom/uibackend/next
const next = require('next')
const app = next({ dev: true, dir: './', conf: { } })
const handle = app.getRequestHandler()
module.exports = router => {
router.all('*', (req, res) => handle(req, res))
}
If i run my app and access the route in the browser it loads for a while and gives a empty response back.
There is just this one log i the console:
info - Using webpack 5. Reason: Enabled by default https://nextjs.org/docs/messages/webpack5
If i build the app and set dev to false it works as expected and i can access the route and the pages.
const app = next({ dev: false, dir: './', conf: { } })
I used the following next.config.js file:
module.exports = {
basePath: '/custom/uibackend/next'
}
My /pages folder is in the root.
Thank you.
I have found the error.
I forgot to call app.prepare()
app.prepare().then(() => {
// add handle to router/app after app.prepare has finished
})

next.js app with custom server is not rendering correctly

I'm new to next.js so maybe I'm missing something very stupid. I want to use custom routes so I created a server.js file and changed my package.json command to node server.js. This is the entire server.js file:
const express = require("express");
const next = require("next");
const createLocaleMiddleware = require("express-locale");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.get("/", createLocaleMiddleware(), (req, res) => {
res.redirect(`/${req.locale.language}/home`);
});
server.get("/:lang/home", (req, res) => {
const actualPage = "/";
const queryParams = { locale: req.params.lang };
app.render(req, res, actualPage, queryParams);
});
server.listen(3000, err => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
I believe that according to the docs, this should work. I just want to render the index page with the users locale on the specified route ('/:lang/home'). I'm using react-intl for the i18n.
Now I get the following error in the console (client side):
It's in dutch but it's just saying it can't find any of the specified files. So now the HMR is not working anymore, routing is not working anymore (with Router.push). The only thing it does correctly is loading the index page (I can see it in the browser).
I also tried to enable and disable this flag from the docs:
module.exports = {
useFileSystemPublicRoutes: false
}
Sadly, no effect.
Am I missing something? Is it because I'm redirecting? Or is this not to way to handle routing? If someone could provide some pointers that would be great :)
Thanks in advance!
You are missing server.get('*', handle) as you can see in the custom server express example. This is absolutely required :)

get List of routes via axios in Vue Webpack Cli

I'm using prerender-spa-plugin in my Vue Webpack Cli project. Like from the documentation i'm registering the Plugin in webpack.prod.conf.js like this
...
plugins: [
...
new PrerenderSpaPlugin(
path.join(__dirname, '../dist'),
['/', '/about', '/contact'],
{
captureAfterTime: 5000
}
)
]
I'm wondering if it would be possible to get the list of routes array via an axios call.
I tried the following without success:
var routes = axios.get('http://myapi.com/api').then(function (response) {
return response.map(function (response) {
return '/base/' + response.slug
})
})
plugins: [
...
new PrerenderSpaPlugin(
path.join(__dirname, '../dist'),
routes,
{
captureAfterTime: 5000
}
)
]
Since my Javascript knowledge is poor I'm not able to solve this. Thankfull for any hints.
Best regards
This currently won't work (or at least work reliably) because Webpack assumes your configuration is synchronous by default. To get around this is to use Webpack's support for asynchronous configuration and return a promise that is resolved after your route request.
If you are in an environment that supports async/await (node 8+) then it's as simple as exporting an async function. Otherwise, return a new Promise:
// webpack.conf.js
module.exports = async function () {
const response = await axios.get('http://myapi.com/api')
const routes = response.map((response) => {
return '/base/' + response.slug
})
return {
plugins: [
// ...
new PrerenderSpaPlugin(
path.join(__dirname, '../dist'),
routes,
{
captureAfterTime: 5000
}
)
]
}
}
If that isn't an option, you can always have a task that makes this request, writes it to a json file, and then require('./route-response.json') in your config.
I had a similar requirement - to get the list of routes from my API. I ended up creating a prebuild script - prebuild.js
const fs = require('fs')
const axios = require('axios')
// Fetch data from API
axios.get('http://myapi.com/api.php')
.then(function(response) {
const data = response.data
try {
// Save the route list to local file
fs.writeFileSync('./route-response.js', data)
} catch(err) {
console.log(err)
}
})
.catch(function(err) {
console.log(err)
})
My API sends the data of the route-response.js file, ready to be directly saved and consumed by npm. You could handle this formatting in Node instead. Sample format:
module.exports = {
theList: [
'/',
'/about',
'/contact',
'/login',
'/register'
]
}
The above file is fetched in webpack.prod.conf.js as below:
...
const routeList = require('./route-response.js')
...
const webpackConfig = merge(baseWebpackConfig, {
...
plugins: [
...
new PrerenderSPAPlugin({
staticDir: path.resolve(__dirname, '../dist'),
routes: routeList.theList,
renderer: new PuppeteerRenderer()
})
...
]
...
})
and finally, add the prebuild script to the package.json
prebuild: runs before the build step.
postbuild: runs after the build step.
Here's a sample
...
"scripts": {
"dev": "node build/dev-server.js",
"prebuild": "node build/prebuild.js",
"build": "node build/build.js"
},
"dependencies": {
...
This way, I only have to run npm run build

Categories