I know there are a lot of other "works in postman and not in browser" posts, but I've read through them and cannot find anything to give direction on what I'm not catching here. Most of those had to do with proxy issues, but I dont have any proxy's set up.
I recently changed from using a pymongo backend to mongoose/express. My find() works for the get all clients just fine on the browser side, but the findOne() get comes back undefined (I was getting an unexpected token JSON error but that is resolved although I dont know what actually fixed it), yet in Postman it brings exactly what I'm looking for. I'm assuming its something simple, but I can't seem to spot it.
Backend-
index.js
const express = require("express")
const mongoose = require("mongoose")
const cors = require('cors')
const clientRoutes = require("./routes/clientRoutes")
const contractRoutes = require("./routes/contractRoutes")
const bodyParser = require('body-parser');
mongoose
.connect("MONGODB URL", { useNewUrlParser: true })
.then(() => {
const app = express()
app.use(express.json())
app.use(cors())
app.use(bodyParser.json());
app.use("/api", clientRoutes)
app.use("/api", contractRoutes)
app.listen(5000, () => {
console.log("Server has started")
})
})
Schema
const mongoose = require("mongoose")
const schema = mongoose.Schema({
clientId: Number,
firstName: String,
lastName: String,
phone: String,
contracts: [{
contractId: Number,
authNumber: String,
contType: String,
contHours: Number,
contStartDate: Date,
contEndDate: Date
}],
})
module.exports = mongoose.model("Client", schema)
routes-
const express = require("express")
const Client = require("../models/Client.js")
const router = express.Router()
//Client routes
router.get("/clients", async (req, res) => {
const clients = await Client.find()
res.send(clients)
})
router.get("/clients/:clientId", async (req, res) => {
try {
const client = await Client.findOne({ clientId: req.params.clientId })
res.send(client)
} catch {
res.status(404)
res.send({ error: "Client not found"})
}
})
React frontend component making the request-
import React from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import ChartNav from './ChartNav';
import ClientContext from './ClientContext';
class ClientChart extends React.Component {
static get propTypes() {
return {
match: PropTypes.any,
clientId: PropTypes.any
}
}
constructor (props){
super(props);
this.state = {
clientId: this.props.match.params.clientId,
client: {},
isLoading: true,
errors: null
};
console.log(this.state.clientId)
}
componentDidMount(){
fetch(`http://localhost:5000/api/clients/${this.state.clientId}`)
.then(res => res.json())
.then(
result => {
let client = JSON.parse(result.data);
this.setState({
isLoading: false,
client: client,
});
}, [])
.catch(error => this.setState({
error: error.message,
isLoading: false,
}));
}
console and response
404
XHR GET http://localhost:5000/api/clients/undefined
error "Client not found"
So in trying to track it down, I switched clientId back to id (which I had been using previously, and changed the prop in the DB for 1 client back to id to test), and calling console.log after the initial response from the fetch showed the data coming through. When I setState from that initial response, all props populated where they should. In reverting the id back to clientId and changing the routes, and using a client with the clientId field, etc., nothing works again. So if anyone knows why React is happy with id but not clientId as an identifier, please let me know. Even weirder is that its able to call all the other clients who I still have listed with clientId, and the routes are calling by clientId, not id... so Im at a total loss as to whats happening under the hood.
Below is the working get call (I also threw in axios at one point in trying to track it down and left it there, but initially it did not make any difference).
axios.get(`http://localhost:5000/api/clients/${this.state.id}`)
.then((response) => {
const data = response.data;
console.log(response.data);
this.setState({
client: data,
isLoading: false,
});
}, [])
Related
I am trying to do Spotify Authentication using Client(React) and Server, the logging in works for a second then the page refreshes immediately after logging in and logs the user out. Anyone knows where might be the problem?
Here is my code:
server.js:
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const SpotifyWebApi = require('spotify-web-api-node');
const app = express();
app.use(cors()) // to handle the cross-origin requests
app.use(express.json()); // to parse JSON bodies
const port = process.env.PORT || 8000;
const credentials = {
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI || "http://localhost:3000"
};
app.post('/refresh', (req, res) => {
const refreshToken = req.body.refreshToken;
// console.log("Hii");
let spotifyApi = new spotifyWebApi({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
redirectUri: process.env.REDIRECT_URI,
refreshToken,
});
spotifyApi
.refreshAccessToken()
.then((data) => {
// console.log(data.body);
res.json({
accessToken: data.body.access_token,
expiresIn: data.body.expires_in,
})
})
.catch((err) => {
console.log(err);
res.sendStatus(400);
});
});
app.post('/login', (req,res) => {
// Get the "code" value posted from the client-side and get the user data from the spotify api
let spotifyApi = new spotifyWebApi(credentials)
const code = req.body.code
spotifyApi.authorizationCodeGrant(code).then((data) => {
// Returning the User's Data in the json formate
res.json({
accessToken : data.body.access_token,
refreshToken : data.body.refresh_token,
expiresIn : data.body.expires_in
})
})
.catch((err) => {
console.log(err);
res.sendStatus(400)
})
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Client side:
useAuth.js:
import React from 'react';
import { useEffect, useState } from 'react';
import axios from "axios"
export default function useAuth(code) {
const [accessToken, setAccessToken] = useState();
const [refreshToken, setRefreshToken] = useState();
const [expiresIn, setExpiresIn] = useState();
useEffect(() => {
axios
.post("/login", {code})
.then((res) => {
window.history.pushState({}, null, "/");
console.log(res.data);
setAccessToken(res.data.accessToken);
setRefreshToken(res.data.refreshToken);
setExpiresIn(res.data.expiresIn);
})
.catch(() => {
window.location = "/";
});
}, [code]);
useEffect(() => {
if (!refreshToken || !expiresIn) {
return;
}
let interval = setInterval(() => {
axios
.post("/refresh", {refreshToken})
.then((res) => {
setAccessToken(res.data.accessToken);
setExpiresIn(res.data.expiresIn);
})
.catch(() => {
window.location = "/";
});
}, (expiresIn - 60) * 1000);
return () => clearInterval(interval)
}, [refreshToken, expiresIn]);
return accessToken;
}
spotifyConfig.js:
const authEndpoint = "https://accounts.spotify.com/authorize";
const redirectUri = "http://localhost:3000";
const clientId = "ea28d4ba34f34b44b59c640052c6e098";
const scopes = [
"streaming",
"playlist-modify-public",
"ugc-image-upload",
"user-read-email",
"user-read-private",
"user-read-currently-playing",
"user-read-recently-played",
"user-read-playback-state",
"user-modify-playback-state"
];
export const loginUrl = `${authEndpoint}?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=${scopes.join(
"%20"
)}&show_dialog=true`;
App.js:
import './App.css';
import Dashboard from './pages/Dashboard';
import Login from './components/Login';
const code = new URLSearchParams(window.location.search).get('code')
function App() {
return (
<div>
{code ? <Dashboard code={code}/> : <Login/>}
</div>
);
}
export default App;
👉🏽 this page appears a second then logs out Dashboard.js:
import React, {useEffect} from 'react';
import useAuth from '../useAuth';
import SpotifyWebApi from "spotify-web-api-node";
const spotifyApi = new SpotifyWebApi({
clientId: "ea28d4ba34f34b44b59c640052c6e098",
});
export default function Dashboard({code}) {
const accessToken = useAuth(code);
useEffect(() => {
if (!accessToken) return;
spotifyApi.setAccessToken(accessToken);
spotifyApi.getMe().then(data => {
console.log(data);
})
}, []);
return (
<div>
This is the home page 🏠
</div>
)
}
Login.js:
import React from 'react';
import { loginUrl } from '../spotifyConfig';
export default function Login() {
return (
<div>
<a href={loginUrl}>
<button>LOGIN WITH SPOTIFY</button>
</a>
<div className="links">
<p>
⚠ When joining or creating a Queue, open Spotify to be able to queue up tracks
</p>
</div>
</div>
)
}
I experienced a similar issue. Here are a few steps that helped me to resolve it and many of the subsequent issues I encountered.
Run your IDE's debugger and set break points for your /login request. Also, check whether your environment variables are getting set as you intend (if running VSCode you can learn how to set this up here). In particular, make sure your credentials
clientId: process.env.CLIENT_ID, clientSecret: process.env.CLIENT_SECRET, redirectUri: process.env.REDIRECT_URI
are correct. If your environment variables are not being loaded you won't be able to create a new SpotifyWebApi instance (check out dotenv file is not loading environment variables). Another easy way to check if this is the problem is to hard code your values temporarily.
Test your server /login endpoint independently before running the client to see whether the endpoint is returning 400 or another error when executing requests to Spotify.
Make sure you keep your devtools console open in your browser so you can identify any failed requests you're making to the Spotify API and consider setting devtools to preserve logs in case the page refreshing is deleting them.
If you are running both your client and server from inside VSCode, try instead running them both in new shells outside of VSCode.
I was having a similar issue, having followed this tutorial, and then this YouTube tutorial.
What I realised was that my App component was being rendered twice, which was causing everything to be called twice, including the login endpoint. I was able to verify this using console.log in the endpoint and seeing if the log appeared twice. As the same Spotify code was being used twice in SpotifyWebApi.authorizationCodeGrant, this was what was causing the error.
I was able to trace the issue to the React.StrictMode being enabled, which must have happened when using the create-react-app command. Verify whether the tags appear in your index.js file. For more information, check this StackOverflow answer: My React Component is rendering twice because of Strict Mode
I am still learning about the MERN stack, and I am making a simple CRUD app using the MERN stack. I added some validators using express-validator for the requests. The problem is, somehow the data is sent and saved to the database but not giving any response when I try it using Postman.
I think the problem is either on the response of the post controller function or the validators. I got no idea where is it exactly though. I've been stuck here for like an hour. Already going back and forth to get some answers on google. but couldnt find any.
here are the codes.
index.js :
import express from 'express'
import mongoose from 'mongoose'
import dotenv from 'dotenv'
import { router as postRoutes } from './routes/post.js'
import { router as authRoutes } from './routes/auth.js'
//setup
const app = express()
app.use(express.json());
dotenv.config();
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin','*')
res.setHeader('Access-Control-Allow-Methods','POST, GET, PUT, DELETE, OPTIONS')
res.setHeader('Access-Control-Allow-Headers', 'Content-Type')
next();
})
//route groups
app.use('/v1/post', postRoutes)
app.use('/v1/auth', authRoutes)
//validation
app.use((error,req,res,next) => {
const status = error.errStatus || 500
const message = error.message
const data = error.data
res.status(status).json({message : message, data: data})
})
//connect to mongodb
mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true })
.then(app.listen(4000, ()=>{ console.log('Server is up!')}))
.catch(err => console.log(err))
Post controller :
*Note: I dont send the whole code of the post controller here, only the create post function
import { validationResult } from 'express-validator'
import PostSchema from '../models/post.js'
//handle post requests
const createPost = (req, res, next) => {
const title = req.body.title
const content = req.body.content
//get errors result from express-validator
const errors = validationResult(req)
if(!errors.isEmpty()){
const err = new Error('Invalid request')
err.errStatus = 400
err.data = errors.array()
throw err
}
const Posts = new PostSchema({
title: title,
content: content,
author: {
_id: 1,
name: 'fratio '
}
})
Posts.save()
.then(result =>{
res.status(201).json({ //the error messsage says there is an error on this line
description: "successfully created",
data : result,
})
})
.catch(err =>console.log(err))
next()
}
export {createPost}
Post routes :
import express from 'express'
import { body } from 'express-validator'
import { createPost, deletePost, getPosts, updatePost } from '../controllers/post.js'
const router = express.Router()
router.get('/', getPosts)
router.post('/create',
[
body('title').isLength({min: 5}).withMessage('minimum length is 5 characters'),
body('content').isLength({min: 5}).withMessage('minimum length is 5 characters')
], createPost)
router.put('/:id/update', updatePost)
router.delete('/:id/delete', deletePost)
export { router }
Post Schema :
import mongoose from 'mongoose'
const Schema = mongoose.Schema
const PostSchema = new Schema({
title: {
type : String,
required : true,
},
content: {
type : String,
required : true
},
author: {
type: Object,
required: true
}
},{
timestamp: true
})
export default mongoose.model("PostSchema", PostSchema)
ERROR MESSAGE :
[nodemon] starting `node index.js`
Server is up!
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at ServerResponse.header (D:\Programming\Web Development\mern-playground\mern-basic-1\server\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (D:\Programming\Web Development\mern-playground\mern-basic-1\server\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (D:\Programming\Web Development\mern-playground\mern-basic-1\server\node_modules\express\lib\response.js:267:15)
at file:///D:/Programming/Web%20Development/mern-playground/mern-basic-1/server/controllers/post.js:28:25
at processTicksAndRejections (internal/process/task_queues.js:95:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
Thank you so much. I hope you guys can help me out, I really got no idea how to fix this. Sorry if the code is hard to read.
You have a problem with your promise logic.
const createPost = (req, res, next) => {
// This will create a promise, when the promise is resolved "then" will be executed
// if the promise is rejected, "catch" will be executed.
// A promise doesn't actually stops the flow of the code here, "next()" will be called
// before the promise is resolved/rejected
Posts.save()
.then(result =>{
res.status(201).json({ //the error messsage says there is an error on this line
description: "successfully created",
data : result,
})
})
.catch(err =>console.log(err));
// "next" is called right after the promise is created
// but the promise doesn't stop the flow of the code
// so this is executed while the promise is still being resolved/reject
next()
}
You are calling next before the promise is resolved/rejected, so the next handler is probably the default 404 handler of express and that is the response you will receive. Later, the promise will be resolved/rejected and you will try to send a response again and that's why the error is thrown.
The solution is simple, move the next() call inside your promise or delete it.
I'm using Axios to query an endpoint in my backend. When I try and do this, I get a 404 not found. If I copy/paste the uri it gives in the error from the console and try and access it directly in the browser it connects fine and does not give me an error (instead giving me an empty object which is expected).
Below is my Axios code
axios.post("/api/myEndpoint", { id: this.userID })
.then((response) => {
this.property = response.data.property;
})
.catch((errors) => {
console.log(errors);
router.push("/");
});
Below is the route definition in my backend
const myEndpointRoute = require('../api/myEndpoint.js')();
exprApp.use('/api/myEndpoint', myEndpointRoute);
For reference, the uri is 'http://localhost:3000/api/myEndpoint'. I can access this uri completely fine in the browser but Axios returns a 404 as described above. It is for this reason that I'm confident this is an issue in the frontend, however I have set up this Axios request in the same way as the many others I have and they all work fine.
Edit: here's the rest of the backend
myEndpoint.js
module.exports = function() {
const express = require('express'), router = express.Router();
const authMiddleware = require('../loaders/authMiddleware.js')();
router.get('/', authMiddleware, async function(req, res) {
const id = req.body.id;
const property = await require('../services/myEndpointService.js')
(id).catch((e) => { console.log(e) });
res.send({ property: property });
});
return router;
};
myEndpointService.js
module.exports = async function(id) {
const results = await require('../models/getMyEndpointProperty')(id);
return results;
};
getMyEndpointProperty
module.exports = async function(id) {
const pool = require('../loaders/pool.js')();
const res = await pool.query(`SELECT * FROM myTable WHERE id = ${id};`);
return res.rows;
};
myEndpoint.js defines only a GET method but your axios call sends a POST in the frontend. Try changing (or adding) the express route:
// notice the `.post`
router.post('/', authMiddleware, async function(req, res) {
...
})
It worked when you manually tested it in the browser for this reason as well, since the browser sent a GET request.
How would I go about rendering a component after an axios POST request? I want to load a new component after the success response has been received from Stripe. I am trying to update the state of my component by adding a setState after receiving the response and load a simple div if the state has any values. The issue I am having is that component is not re-rendering when I use the setState.
Below is how I have a stripe component setup and the express server:
import StripeCheckout from 'react-stripe-checkout';
import axios from 'axios';
import './stripe-button.styles.scss';
import { createStructuredSelector } from 'reselect';
import { selectCurrentUser } from '../../redux/user/user.selectors';
import { setCurrentUser } from '../../redux/user/user.actions';
class StripeCheckoutButton extends React.Component {
constructor(props) {
super(props);
this.state = {
cardListBacklog: []
};
}
onToken = token => {
console.log(token);
const { cartItems, price } = this.props;
const priceForStripe = price * 100;
const orderSummary = cartItems.reduce(
(cartItemAll, cartItem) =>
(cartItemAll += cartItem.name + cartItem.quantity),
''
);
axios({
url: 'payment',
method: 'post',
data: {
amount: priceForStripe,
order: orderSummary,
token
}
})
.then(response => {
alert(
`Payment successful, ${response.data.success.billing_details.name}; please check your email for your receipt.`
);
this.setState({cardListBacklog: response.data});
})
.catch(error => {
console.log('Payment error: ', JSON.parse(error));
alert('There was an issue with your payment. Please try again!');
});
};
render() {
const publishableKey = 'pk_test_gxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const { price } = this.props;
const priceForStripe = price * 100;
return (
this.state.cardListBacklog.length
?
<div>Payment Successful</div>
:
<StripeCheckout
label="Pay Now"
name="Ltd."
billingAddress
shippingAddress
image="https://i.imgur.com/vWgUzv.png"
description={`Your total is $${price} USD`}
amount={priceForStripe}
panelLabel="Pay Now"
token={this.onToken}
stripeKey={publishableKey}
label="Pay with 💳"
/>
);
}
}
export default StripeCheckoutButton;
Here is my Server.js:
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const path = require('path');
if (process.env.NODE_ENV !== 'production') require('dotenv').config();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
const port = process.env.PORT || 5000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, 'client/build')));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'client/build', 'index.html'));
});
}
app.listen(port, error => {
if (error) throw error;
console.log('Server running on port: ' + port);
});
app.post('/payment', (req, res) => {
const body = {
source: req.body.token.id,
amount: req.body.amount,
receipt_email: req.body.token.email,
description: req.body.order,
currency: 'usd'
};
stripe.charges.create(body, (stripeErr, stripeRes) => {
if (stripeErr) {
res.status(500).send({ error: stripeErr });
} else {
res.status(200).send({ success: stripeRes });
}
});
});
this.state.cardListBacklog.length
This is the issue. Its giving 0 before update, and undefined after its updated. Console log and check if its true.
this.state = {
cardListBacklog: false
};
and
this.setState({cardListBacklog: true});
should do the trick.
I guess, you expects stripeRes in the state, but you are receiving an object
{success: stripeRes} instead.
You response with an object from the server here
res.status(200).send({ success: stripeRes });
But on the client side in the state you expect the array, not an object.
this.state.cardListBacklog.length
Object doesn't have length property by default.
You should check something else on the client. Maybe you should update state
on success response like
this.setState({cardListBacklog: response.data.success });
This is not super cool, but should give you an idea that client side and server side expect different things.
You should rethink your API.
The idea to use flag of successful response here (https://stackoverflow.com/a/59011695/10559239) makes sense to you, if you doesn't want to use response data in near future. Good as a first step.
But the main problem, as I can see is inconsistency between server and client.
I am a beginner in VueJs and Expressjs. I am trying to make frontend side by Vuejs and backend by ExpressJs. I send a post request to the backend (expressJs) and :
1- Response is undefined
2- At the same time I can see 2 requests in chrome development tools. One is Option and another one is Post.
3- With postman there is no problem at all.
Here is the code of app.js in express
console.log('Server is running')
const express = require('express'),
bodyParser = require('body-parser'),
cors = require('cors'),
morgan = require('morgan');
app = new express();
//Setup middleware
app.use(cors());
app.use(morgan('combined'))
app.use(bodyParser.json())
app.post('/register', (req, res, next) => {
res.send({
message: `Hello ${req.body.email}! your user was registered!`
})
});
app.listen(8081);
And here is the code in VueJs :
// Api Setting
import axios from 'axios'
export const HTTP = axios.create({
baseURL: `http://localhost:8081`
});
// AuthenticationService
import { HTTP } from '../services/Api'
export default {
register(credentials) {
HTTP.post('register', credentials);
}
}
// Register Component
export default {
data() {
return {
email: '',
password: ''
};
},
methods: {
async register() {
const response = await AuthenticationService.register({
email: this.email,
password: this.password
});
console.log(response); // the value is undefined
}
}
};
I really don't know what I missed here that I get an undefined response and 2 requests at the same time. I appreciate any hint.
Whole code on github repo : here
Maybe. Authentication.register is not returning anything or more specifically a Promise which should be used to populate const response in the await call.
Try returning something like so: return HTTP.post('register', credentials); inside register.
For this to work though, HTTP.post('register', credentials) should also return something.
I use JSON.stringify to send the data, you are sending the objects directly, so
register(credentials) {
HTTP.post('register', credentials);
}
becomes
register(credentials) {
HTTP.post('register', JSON.stringify(credentials));
}