Home > Mobile >  Problem with MERN app on netlify and heroku (CORS)
Problem with MERN app on netlify and heroku (CORS)

Time:04-27

This my first question but I completely have noo idea what to do :/ I learn javascript technologies. I've written my MERN app where I handle login and register feature. My backend is deployed on heroku but client side is deployed on netlify. Everything is working fine locally, but when I test my app after deployment to heroku and netlify, everything is ok till I try send a request to my backend (for example during login process). My request is pending aproximately 20-30 sec and after this time I receive annoucement with this content - "Access to XMLHttpRequest at 'https://pokemontrainer-app.herokuapp.com/auth/signin' from origin 'https://pokemon-trainer-mern-app.netlify.app' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.". I've been looking for a solution. Most often I saw infos about _redirects file for client build folder for netlify. Unfortunately documentation is very short and unclear when it comes to this issue. Maybe one of you had a similar problem and resolved it with success? If _redirects file is really solution, can I ask for short ifnormation how I should prepare it?

this is my backend code:

server.js file:

const express = require('express');
const cors = require('cors');
const mongoose =  require('mongoose');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser')
const mainRoutes = require('./routes/main.js');
const signinSignupRoutes = require('./routes/signInSignUp.js');
const userTrainersRoutes = require('./routes/userTrainers.js');
require('dotenv').config({ path: './.env' });

const app = express();
const port = process.env.PORT || 8000;

console.log(process.env.FRONTEND_URI);

//------Express-------

app.use(bodyParser.json({limit: '500mb'}));
app.use(bodyParser.urlencoded({limit: '500mb', extended: true}));
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded());
app.use(cors(
  {
    credentials: true,
    origin: 'https://pokemon-trainer-mern-app.netlify.app'
  })
);

app.use('/', mainRoutes);
app.use('/auth',  signinSignupRoutes);
app.use('/loggedUser', userTrainersRoutes);

//------Mongoose-------

const main = async() => {
  try {
    await mongoose.connect(`mongodb srv://${process.env.USERS_USERNAME}:${process.env.USERS_API_KEY}@pokemon-app.2s1cy.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`);
    console.log('Database connection works!')
  }catch(err) {
    console.log(err.message);
  }
}

main()
.then(()=> app.listen(port, () => {
  console.log(`Server works on port ${port}`);
}))
.catch(err => console.log(err.message));

signIn&Up.js file:

const bcrypt = require('bcryptjs');
const Joi = require('joi');
const jwt = require('jsonwebtoken');
const {User, validation} = require('../models/user.js');

const getUsers = async (req, res) => {
  
  try {
    const users = await User.find();
    res.status(200).json(users);
  } catch(err) {
    res.status(404).json(err.message);
  }
}

const signUp = async(req, res) => {
  
  try{
    const {error} = validation(req.body);
    error && res.status(400).send({message: error.details[0].message});
    const user = await User.findOne({email: req.body.email});
    if(user) {
      res.status(409).send({message: 'User with this email already exists.'})
    } else {
      if(req.body.userName === "") {
        res.status(400).send({message: `Username field is empty`});
      } else if(req.body.password !== req.body.confirmPassword || req.body.password === "") {
        res.status(400).send({message: `Passwords aren't the same or password field is empty`});
      } else {
        const hashedPassword = await bcrypt.hash(req.body.password, 12);
        await User.create({email: req.body.email, userName: req.body.userName, password: hashedPassword});
        res.status(201).send({message: 'User registered succesfully!'});
      }
    }
  } catch(err) {
    res.status(500).send({message: 'Internal server error :('});
  }
}

const signIn = async(req, res) => {
  res.setHeader('Access-Control-Allow-Origin', 'https://pokemon-trainer-mern-app.netlify.app');

  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');

  res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');

  res.setHeader('Access-Control-Allow-Credentials', true);
  
  try{
    const {error} = signInValidation(req.body);
    error && res.status(400).send({message: error.details[0].message});
    const user = await User.findOne({email: req.body.email});
    !user && res.status(401).send({message: 'User with this email adress is not registered :('});
    const validatedPassword = await bcrypt.compare(req.body.password, user.password);
    !validatedPassword && res.status(401).send({message: 'Incorrect password :('});
    const token = await user.generateAuthToken(user._id, user.email);
    res.cookie('token', token, {
      maxAge: 7 * 24 * 60 * 60 * 1000,
      httpOnly: process.env.NODE_ENV === 'production' ? true : false,
      secure: false,
    }).status(200).send({message: 'Log in succesfully', userData: {userId:user._id, userName: user.userName, email: user.email, trainers: user.trainers, logged: true}});
  } catch(err) {
    console.log(err.message);
  }
}

const signInViaGoogle = async(req, res) => {
  
  try{
    const user = await User.findOne({email: req.body.email});
    !user && res.status(401).send({message:'You have to register your account with this email in this app'});
    const token = user.generateAuthToken(user._id, user.email);
    res.cookie('token', token, {
      maxAge: 7 * 24 * 60 * 60 * 1000,
      httpOnly: process.env.NODE_ENV === `${production}` ? true : false,
      secure: false,
    }).status(200).send({message: 'Log in succesfully', userData: {userId:user._id, userName: user.userName,email: user.email, trainers: user.trainers, logged: true}});
  } catch(err) {
    res.status(500).send({message: 'Internal server error :('});
  }
}

const logout = async(req, res) => {
  
  try {
    const {token} = req.cookies;
    token && res.clearCookie('token').send({message: 'Cookie cleared'});
  } catch(err) {
    res.status(500).send({message: 'Internal server error :('});
  }
}

const newSession = async(req, res) => {
  
  const {token} = req.cookies;
  !token ? res.status(200).send({cookie: false, logged: false}) : res.status(200).send({cookie: true, logged: true});
}

// validation for signIn

const signInValidation = (data) => {
  const JoiSchema = Joi.object({
    email: Joi.string().required().label('E-mail'),
    password: Joi.string().required().label('Password'),
  });
  return JoiSchema.validate(data);
}

module.exports = {getUsers, signUp, signIn, signInViaGoogle, logout, newSession}

client side code:

apiHandling.js file:

import axios from 'axios';
import { loginNativeUser, updateUserData, newSession } from '../actions/userActions.js'

const url = 'https://pokemontrainer-app.herokuapp.com';

const instance =  axios.create({
    baseUrl: url,
    withCredentials: true,
    credentials: 'include',
})

export const newSess = async (dispatch) => {
   await instance.get(`${url}/auth/newSession`)
   .then(res => {
       dispatch(newSession(res.data));
   })
   .catch(err => console.log(err.message));
}

export const signInByGoogle = async (userData, setError, history, dispatch) => {
    await instance.post(`${url}/auth/signin/google`, {
        email: userData.email,
    })
    .then(res => {
        setError(null);
        dispatch(loginNativeUser(res.data.userData));
        history.push('/');
    })
    .catch(err => {
        setError(err.response.data.message);
        history.push('/auth/signin');
        alert(err.response.data.message);
    })
}

export const signIn = async (formData, setError, history, dispatch) => {
    await instance.post(`${url}/auth/signin`, {
        password: formData.password,
        email: formData.email,
    })
    .then(res => { 
        setError(null);
        dispatch(loginNativeUser(res.data.userData));
        history.push('/');
    })
    .catch(err => {
        setError(err.response.data.message);
        history.push('/auth/signin');
        alert(err.response.data.message);
    });
}

export const signUp = async (formData, setError, history) => {
    await instance.post(`${url}/auth/signup`, {
        userName: formData.userName,
        password: formData.password,
        confirmPassword: formData.confirmPassword,
        email: formData.email,
    })
    .then(res => { 
        setError(null);
        history.push('/');
        alert('Registered succesfully')
    })
    .catch(err => {
        setError(err.response.data.message);
        history.push('/auth/signup');
        alert(err.response.data.message);
    });
}

export const cookieClear = async () => {
    await instance.get(`${url}/auth/deleteCookie`)
    .then(res => {
        console.log('Cookie cleared');
    })
    .catch(err => {
        console.log(err.response.data.message);
    });
}

export const addTrainer = async (userId, trainer) => {
    await instance.patch(`${url}/loggedUser/${userId}/addTrainer`, {
        userId: userId,
        trainer: trainer
    })
    .then(res => {
        alert('Trainer added');
    })
    .catch(err => {
        alert(err.response.data.message);
    });
}

export const removeTrainer = async (userId, trainerId) => {
    await instance.patch(`${url}/loggedUser/${userId}/${trainerId}/removeTrainer`, {
        userId: userId,
        trainerId: trainerId
    })
    .then(res => {
        alert(res.data.message);
    })
    .catch(err =>{
        alert(err.response.data.message);
    })
}

export const addPokemon = async(userId, trainerId, pokemon) => {
    await instance.patch(`${url}/loggedUser/${userId}/${trainerId}/${pokemon}/addPokemon`, {
        userId: userId,
        trainerId: trainerId,
        pokemon: pokemon
    })
    .then(res => {
        alert('Pokemon caught');
    })
    .catch((err) => {
        alert(err.response.data.message);
    })
}

export const updateData = async (userId, dispatch) => {
    await instance.post(`${url}/loggedUser/${userId}/updateData`, {
        userId: userId,
    })
    .then(res => {
        dispatch(updateUserData(res.data.userData));
    })
    .catch(err => {
        console.log(err.response.data.message);
    });
}

If it is needed, I can also send a github link with code.

Thank you in advance for your answers.

CodePudding user response:

You can add a middleware into you express app. I happened to write some cors config for my express api today. For your reference (server side code):

const corsMiddleware = (req, res, next) => {
  res = applyCorsHeaders(res);
  if (req.method === 'OPTIONS') {
    res.status(200).end()
    return
  }
  next()
}

const applyCorsHeaders = res => {
  res.setHeader('Access-Control-Allow-Credentials', true);
  res.setHeader('Access-Control-Allow-Origin', '*')
  // or res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
  res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT')
  res.setHeader(
    'Access-Control-Allow-Headers',
    'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
  )
  return res;
}

Then use this middleware in your app:

app.use(corsMiddleware);

For more about CORS, here's one official intro: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

CodePudding user response:

Thank you for all responses. I decided to put my whole app on heroku. It works. CORS policy problem disappeared. Problem was related to different domains (heroku for backend and netlify for frontend). I decided to use this solution because I have never tried to put frontend on heroku. It required a rearrangement of my project folder. Now my project structure is:

enter image description here

  1. Go to Heroku.com
  2. Click on your "Dashboard"
  3. Click "New"
  4. Click "Create New App"
  • Give the app a name and click "Create App"
  1. in server.js file add:

const path = require("path");
    
    //
    
    app.use(express.static(path.join(__dirname, "client", "build")));
    
    //
    
    app.get("*", (req, res) => {
        res.sendFile(path.join(__dirname, "client", "build", "index.html"));
    });
    
    // remember - in heroku app set a port like this - 
       const port = process.env.PORT || 5000;

  1. in client package.json file add "proxy":

"proxy": "http://localhost:8000" - or other url of your server

  1. set up enviroment variables in heroku app:

Go to "Settings" Click "Reveal Config Vars" Add a new variable and click "Add".

  1. add next script in package.json in root folder:

"scripts": {
        "heroku-postbuild": "cd client && npm install --only=dev && npm install && npm 
         run build"
    }

  1. in Procfile folder:

 web: node server.js

  1. in root folder heroku git:remote -a app-name-from-heroku git init git add . git commit -m "commit name" git push heroku main (or master)

You app should works. I found this clear solution in coursework.vschool.io (article title - Deploying MERN App to Heroku (using your Git Master Branch & MongoDB Atlas).

  • Related