Deploying react/apollo-server app onto Heroku - reactjs

What I'm trying to do is deploy react / apollo-server full stack app to heroku. Thus I' trying to serve static client files from express/apollo-server backend, like this:
const path = require('path');
const express = require('express');
const app = express();
const cors = require('cors');
const { ApolloServer } = require('apollo-server');
const { schema } = require('./schema');
const { resolvers } = require('./resolvers');
app.use(cors());
app.use(express.static('public'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'public', 'index.html'));
});
const server = new ApolloServer({
typeDefs: schema,
resolvers,
});
server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
For some reason I don't understand client is not served while deployed to heroku. On heroku URL I'm getting: GET query missing. If I set graphql to enabled in production I can see it, I can play with resolving data. But client is not rendered. I'm guseing app.get with * is not working and then index.html is not catched.
How can I fix that?
Thanks!

The error you are getting is because you are only exposing the server from ApolloServer to the port 4000 and not exposing the app with the frontend client application.
In order to have the fullstack app deployed, you also have to expose the app, in order to do that you can use applyMiddleware from ApolloServer and bind both apollo server and the frontend client, something like:
.....
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'public', 'index.html'));
});
const server = new ApolloServer({
typeDefs: schema,
resolvers,
});
server.applyMiddleware({
path: '/my-frontend', // you should change this to whatever you want
app,
});
app.listen({ port: process.env.PORT || 4000 }, () => {
console.log(`🚀 Server ready at http://localhost:4000`);
});
Now you should be able to navigate to http://localhost:4000/my-frontend and see your client application.

Related

React app deployed on Heroku gives 404. Using json-server and express

I am using json-server alongside express to create a mock database. The app works correctly when the base url and the api endpoints are hit but when any other url is hit Heroku returns 404. So for example if https://invoice-app-88.herokuapp.com/something is hit then Heroku returns 404 and app.get('*') will not put me back to the static index.html.
The base url is https://invoice-app-88.herokuapp.com/
The api endpoints are
https://invoice-app-88.herokuapp.com/invoices
and
https://invoice-app-88.herokuapp.com/invoices:id
This is my server.js file containing my express and json-server code
const jsonServer = require("json-server");
const path = require("path");
const express = require("express");
const app = express();
const router = jsonServer.router("db.json");
const PORT = process.env.PORT || 3000;
app.use("/", express.static("build"), router);
if (process.env.NODE_ENV === "production") {
app.get("*", function (req, res) {
res.sendFile(path.resolve(__dirname + "./build/index.html"));
});
}
app.listen(PORT, () => console.log(`Server started on PORT ${PORT}`));
EDIT: Made edit for clarification of issue.

Hiding my MongoURI variable in .env breaks my app on Heroku

I need help with this.
My original setup exposed my mongoURI.
I had a root folder called config that had a file called default.json
default.json
{
"mongoURI": "mongodb+srv://name:pass#app-name.mongodb.net/test?retryWrites=true&w=majority",
"jwtSecret": "secretKey"
}
I used that mongoURI variable in /server.js
server.js
const mongoose = require('mongoose');
const express = require('express');
const app = express();
const config = require('config'); // <<~~ to access /config dir
const cors = require('cors');
const path = require('path');
// Middleware
app.use(express.json());
// DB Config
// You can get all your config/ values with config.get('')
const db = config.get('mongoURI'); // <<~~ access mongoURI var
// Connect to MongoDB
mongoose
.connect(process.env.URI || db, { // <<~~ use mongoURI var
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
})
.then(() => console.log("Connected to MongoDB.."))
.catch(err => console.log(err));
// Available Routes
const tournaments = require('./routes/api/tournaments');
const users = require('./routes/api/users');
// Use Routes
app.use(cors());
app.use('/tournaments', tournaments);
app.use('/users', users);
// For Heroku deployment
if(process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, 'client', 'build')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'client', 'build', 'index.html'))
});
};
// Run Server
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server started on port: ${port}`));
I had that all pushed up to the repo at first, but my app is finished so I'm tying up loose ends so I want to hide that var.
First Attempt
I put the /config directory in .gitignore
ran git rm -rf --cached .
ran git add .
I did that since the file I was trying to gitignore had already been published to the repo, so I had to do this to clear that and re-push the entire app to github, this time actually hiding /config
RESULT
That worked on localhost, and the file was correctly hidden from GitHub.
BUT this actually broke my deployed Heroku version of the app. The app broke.
Second Attempt
Reverted that change
Installed dotenv library
Created a file .env and set my variables:
.env
MONGO_URI="mongodb+srv://name:pass#app-name.mongodb.net/test?retryWrites=true&w=majority"
JWT_SECRET="secretKey"
Replaced the call to /config in server.js to utilize this new ENV setup:
server.js
const mongoose = require('mongoose');
const express = require('express');
const app = express();
// const config = require('config'); // <<~~removed this
const cors = require('cors');
const path = require('path');
const dotenv = require('dotenv'); // <<~~new line
// Middleware
app.use(express.json());
// DB Config
// You can get all your config/ values with config.get('')
// const db = config.get('mongoURI'); // <<~~removed this
dotenv.config(); <<~~new line
const url = process.env.MONGO_URI; <<~~new line
// Connect to MongoDB
mongoose
.connect(url, { <<~~tried this way
.connect(process.env.URI || url, { <<~~and this way
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
})
.then(() => console.log("Connected to MongoDB.."))
.catch(err => console.log(err));
// Available Routes
const tournaments = require('./routes/api/tournaments');
const users = require('./routes/api/users');
// Use Routes
app.use(cors());
app.use('/tournaments', tournaments);
app.use('/users', users);
// For Heroku deployment
if(process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, 'client', 'build')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'client', 'build', 'index.html'))
});
};
// Run Server
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Server started on port: ${port}`));
RESULT
Works fine on localhost,
Heroku app starts without breaking,
BUT the app is not accessing data from mongodb.
I'm not sure what I did wrong. Sorry for lengthy post. Help! And thanks
It is a good practise to use .env only for local development and add it to .gitignore to avoid pushing to the source repository.
Define the same local env variables as Config Vars: this can be done via the Heroku CLI or in the Heroku Dashboard (Web UI). Once deployed your application can access the env variables as you do locally (i.e. access DATABASE_URL config var with process.env.DATABASE_URL).
This is also handy to keep separate your local dev from your Heroku configuration, as normally they would have different settings (dev vs prod).

google cloud trace not working in express

I'm trying to implement google trace in my MERN project, hosted in google cloud platform.
I've followed the guide and the examples offered by Google, but I can't make it work in any way.
import gCloudTrace from '#google-cloud/trace-agent';
// initialize gcloud trace
if (process.env.NODE_ENV === 'production') {
console.log(`Starting google cloud trace...`);
const tracer = gCloudTrace.start();
console.log(JSON.stringify(tracer,null,2));
};
import express from 'express';
import apiRoute from './api.js';
import indexRoute from './index/indexRoute.js';
try {
// setup
const host = process.env.HOST || '0.0.0.0';
const port = process.env.PORT || 5050;
const app = express();
// routes
app.use('/', indexRoute);
app.use('/api', checkGloblalSettings, apiRoute);
// create and run HTTP server
app.listen(port, host, () => {
console.log(`EXPRESS SERVER HTTP BACKEND UP AND RUNNING ON ${host} : ${port}`);
});
} catch(error) {
globalErrorHandler(error);
};
When I deploy (google app engine, flex env) everything works great, all route works but I can't find any trace in my project.
I've double checked gcloud settings: Trace API is enabled, no need to create custom credentials.
Any suggestion?
I finally solved the problem.
While the code in the question seems correct, the problem inherently lies in how NodeJS handles ES6 import() instead of require().
Throughout the Google Trace documentation you will find only examples with require().
Here an example:
if (process.env.NODE_ENV === 'production') {
require('#google-cloud/trace-agent').start();
}
const express = require('express');
const got = require('got');
const app = express();
const DISCOVERY_URL = 'https://www.googleapis.com/discovery/v1/apis';
// This incoming HTTP request should be captured by Trace
app.get('/', async (req, res) => {
// This outgoing HTTP request should be captured by Trace
try {
const {body} = await got(DISCOVERY_URL, {responseType: 'json'});
const names = body.items.map(item => item.name);
res.status(200).send(names.join('\n')).end();
} catch (err) {
console.error(err);
res.status(500).end();
}
});
// Start the server
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
The fact is that while using require the import order is respected (so if you place require('# google-cloud/trace-agent').start(); in the first place everything works), using import() the code runs AFTER importing the express module as well, so nothing is tracked.
To solve, I recreated the require syntax (here the documentation), then using require() everything worked perfectly:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
if (process.env.NODE_ENV === 'production') {
// [START trace_setup_nodejs_implicit]
require('#google-cloud/trace-agent').start({
logLevel: 2,
flushDelaySeconds: 30,
ignoreUrls: ['/_ah/health', '/api/status'],
ignoreMethods: ['OPTIONS'],
samplingRate: 10,
bufferSize: 1000,
onUncaughtException: 'ignore',
});
// [END trace_setup_nodejs_implicit]
};
const express = await require('express');
I hope this can serve others and avoid the headaches it caused me :)

Third-party API won't work after deployment

I have built and deployed a basic portfolio website to Heroku. In this app, I use a third-party API for sending emails (sendgrid) in the contact form. When I was running my app locally on localhost:3000, the API was working as anticiapted and would send me emails. However, once I deployed my app to Heroku, I recieve this error. When I run heroku logs --tail in my terminal, I get this.
Inexcplicably, the API has now stopped working when I run it locally as well. I see error 503 means network error so I am somehow not making a request to the server? The only thing I have changed that I could think would break it is:
I removed the react router to change pages and instead used react-scroll. Maybe this messed with the routes I use in my contact.js file to send the object to sendgird?
I moved my API key to .env rather than storing it in my global environmental variables. I've used dotenv to transpile this but maybe i went wrong somewhere? When I run console.log(process.env.REACT_APP_SENDGRID_API_KEY), it logs the correct key.
This is where I call the API from the contact.js form:
emailApi = () => {
let sendgridObj = {
to: 'caseyclinga#gmail.com',
from: this.state.from,
subject: this.state.subject,
text: this.state.text
}
this.resetState();
axios.post('/', sendgridObj)
.then(res => console.log(`CONTACT.JS RESPONSE: ${res}`))
.catch(err => console.log(`CONTACT.JS ERROR: ${err}`));
}
This is the server.js file:
const express = require('express');
var app = express();
const SgMail = require('#sendgrid/mail');
const path = require('path');
require('dotenv').config();
SgMail.setApiKey(process.env.REACT_APP_SENDGRID_API_KEY);
//Middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
const PORT = process.env.PORT || 5000;
//Serve static assets if in production
if(process.env.NODE_ENV === 'production') {
//Set static folder
app.use(express.static('client/build'))
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'))
})
}
app.post('/', (req, res) => {
console.log(req.body)
SgMail.send(req.body)
.then(res => console.log(`SERVER.JS RESPONSE: ${res}`))
.catch(err => console.log(`SERVER.JS ERROR: ${err}`));
});
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
Here is my deployed site: https://still-sands-49669.herokuapp.com/
Here is my code: https://github.com/caseycling/portfolio

Localhost not connecting to mongodb after deploying to Heroku

I was running off localhost fine with mongodb and then switched to mLab and that was fine too, but now trying to run the database off localhost again I get a 422 error when it tries to connect.
GET http://localhost:3000/api/wines 422 (Unprocessable Entity)
I deployed this site once before and was able to switch back and forth so not sure what is wrong.
This is my server.js code:
const express = require("express");
const path = require("path");
const PORT = process.env.PORT || 3001;
const app = express();
const mongoose = require("mongoose");
const routes = require("./routes");
// Connect to the Mongo DB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/wineDB', { useNewUrlParser: true });
mongoose.connection.on("open", function (ref) {
console.log("Connected to mongo server.");
});
mongoose.connection.on('error', function (err) { console.log(err) });
// require("./models/wine");
// Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
// Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
// Add routes, both API and view
app.use(routes);
// Define API routes here
// Send every other request to the React app
// Define any API routes before this runs
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
app.listen(PORT, () => {
console.log(`🌎 ==> API server now on port ${PORT}!`);
});
I'm wondering if somehow my code is trying to do production mode that is needed to connect to mlab and no longer allowing me to connect to DB through localhost?
Actually, I was wrong about the source. It was because I had port 3001 running somewhere in the background so when I killed it that fixed the problem!

Resources