I am attempting to follow this tutorial (https://levelup.gitconnected.com/introduction-to-express-js-a-node-js-framework-fa3dcbba3a98) to connect Express with React Native via . I have a server.js script running which connects to the client (App.tsx) on my ip, port 3000. The server and app are run simultaneously on the same device in different terminals. The server is able to recieve GET requests just fine, as when the app launches, a useEffect function calls a GET request, and the server sends a message. However my POST requests, which contain a content body set to JSON.stringify("hello world") are not working. and the server script returns the following error anytime I press the button:
SyntaxError: Unexpected token h in JSON at position 0
at JSON.parse (<anonymous>)
...
I'm assuming I am sending in badly formatted json, or haven't set the content type properly, but I haven't been able to figure out the exact problem.
App.tsx (where myip is my ip address):
import { StatusBar } from 'expo-status-bar';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, View, ScrollView, TouchableOpacity, TextInput } from 'react-native';
export default function App() {
const [response, setResponse] = useState();
useEffect(() => {
fetch("http://myip:3000/get")
.then(res => res.json())
.then(res => console.log(res.theServer))
}, []);
async function handleSubmit() {
console.log('button press');
const response = await fetch("http://myip:3000/wow/post", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify("hello world")
});
const body = await response.text();
setResponse({ responseToPost: body });
}
return (
<View style={styles.container}>
<TouchableOpacity onPress={handleSubmit}>
<Text>Submit</Text>
</TouchableOpacity>
</View>
}
...
});
server.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const port = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get("/get", (req, res) => {
res.send({ theServer: "hElLo FrOm YoUr ExPrEsS sErVeR" });
});
app.post("/wow/post", (req, res) => {
console.log(req.body);
res.send(`Here is what you sent me: ${req.body.post}`);
});
app.listen(port, () => console.log(`listening on port ${port}`));
First, stop using body-parser. Express has its own request body parsing middleware.
app.use(express.json())
app.use(express.urlencoded()) // extended = true is the default
The JSON parsing middleware is configured to handle objects by default. While a string literal like "hello world" is valid JSON, it's not what the framework expects, hence your error.
Since you appear to be trying to access req.body.post, you should send your data with such a structure
fetch("http://myip:3000/wow/post", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ post: "hello world" })
})
Alternatively, if you did want to post a JSON string literal, you would need to configure your JSON middleware like so
app.use(express.json({ strict: false }))
strict
Enables or disables only accepting arrays and objects; when disabled will accept anything JSON.parse accepts.
in which case your "hello world" string would appear in req.body
for me, adding
res.header('Access-Control-Allow-Origin', '*');
solved the problem
Related
I am pretty new to Programming and have some questions regarding the MERN stack.
I am building an app and trying to realize the log in via google. I was successful with integrating the google auth to my frontend and now I want to store the user after a successful login in the backend.
The first question I have is do I need a database to store the user, or is it common to just store them on the express backend?
In the auth process I do get the JWT from google and try sending it to the backend, but it does not work.
I do get the following error: "SyntaxError: Unexpected token o in JSON at position 1".
How can I send the JWT to the backend and check, if the user already exists in MongoDB and if he does not exist, make a new entry for the user. And when he is logged in, make a session so he does not have to log in again after every refresh.
At the moment I got the following code for the frontend:
import './App.css';
import { useEffect, useState } from 'react'
import jwt_decode from 'jwt-decode';
import Survey from './components/survey';
function App() {
const [ user, setUser] = useState({});
const [backendData, setBackendData] = useState([{}]);
// fetch backend API, we can define relative route, as proxy is defined in package.json
useEffect(() => {
fetch("http://localhost:5000/api").then(
response => response.json()
).then(
data => {
setBackendData(data)
}
)
}, [])
// store the JWT and decode it
function handleCallBackResponse(response){
console.log("Encoded JWT ID token: " + response.credential);
var userObject = jwt_decode(response.credential);
console.log(userObject);
setUser(userObject);
document.getElementById("signInDiv").hidden = true;
fetch("http://localhost:5000/user", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: userObject,
})
}
// logout the user and show sign in button, google.accounts.id.disableAutoSelect is recording the status in cookies. This prevents a UX dead loop.
function handleSignOut(event){
setUser({});
document.getElementById("signInDiv").hidden = false;
google.accounts.id.disableAutoSelect();
}
useEffect(() => {
/* global google */
// The google.accounts.id.initialize method initializes the Sign In With Google client based on the configuration object.
google.accounts.id.initialize({
client_id: "CLIENT_ID",
callback: handleCallBackResponse
});
// The google.accounts.id.renderButton method renders a Sign In With Google button in your web pages
google.accounts.id.renderButton(
document.getElementById("signInDiv"),
// if only want to display icon
//{theme: "outline", size: "medium", type: "icon"}
{theme: "outline", size: "medium", text: "signin", shape: "square"}
);
// The google.accounts.id.prompt method displays the One Tap prompt or the browser native credential manager after the initialize() method is invoked.
google.accounts.id.prompt();
}, [])
// If we have no user: show sign in button
// if we have a user: show the log out button
return (
<div className="App">
<div id = "signInDiv"/>
{ Object.keys(user).length !== 0 &&
<button className ='signout' onClick={ (e) => handleSignOut(e)}>
{ user &&
<img className='logout' alt="googleprofile" src={user.picture} width='30px' height='30px'></img>
}
</button>
}
<Survey></Survey>
{(typeof backendData.users === 'undefined') ? (
<p>Loading</p>
) : (
backendData.users.map((user, i) => (
<p key={i}>{user}</p>
))
)}
</div>
);
}
export default App;
Backend:
const express = require('express')
const morgan = require('morgan')
const cors = require("cors")
const mongoose = require("mongoose")
const { OAuth2Client } = require("google-auth-library");
const jwt = require("jsonwebtoken");
const app = express()
const uri = "MONGODBURL"
// connect to mongoDB
async function connect() {
try {
await mongoose.connect(uri)
console.log("Connected to MongoDB")
} catch (error) {
console.error(error)
}
}
connect()
// setup view engine, file in "views" folder needs to have ending .ejs
app.set('view engine', 'ejs')
// log requests in the terminal for troubleshooting
app.use(morgan('combined'))
// allow cors URLs
app.use(cors({
origin: ['http://localhost:3000', 'https://play.google.com', 'https://accounts.google.com'],
methods: ["GET", "POST", "PUT", "DELETE"],
credentials: true
}))
app.use(express.json());
// start app on port 5000 and give an log message
app.listen(5000, () => {console.log("Server started on port 5000") })
Hope this make clear what I want to achieve. I really appreciate any help.
Kind Regards
New to Axios and Node as a backend- have inherited some code in React for a login page and am trying to figure out why I can't make a POST to the backend.
This is my .env file:
REACT_APP_BACKEND_URL=http://localhost:3000/admin
And there is a file called API.js
import axios from "axios";
// Set config defaults when creating the instance
export const CompanyAPI = () => {
let api = axios.create({
baseURL: process.env.REACT_APP_BACKEND_URL,
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
return api;
};
And then there is a LoginPage.js:
export const LoginPage = () => {
const API = CompanyAPI();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const getAuth = (e) => {
e.preventDefault();
API.post("/auth/login", {
email: email,
password: password,
})
.then(async (response) => {
if (response.status === 200) {
await localStorage.setItem("jwt_key_admin", response.data.token);
setTokenKey(response.data.token);
setIsAuth(true);
navigate("/");
}
})
.catch((error) =>
alert(
"Login Failed"
)
);
};
My question is, is there an example on how I could use express to handle the /auth/login endpoint and complement the existing API.js file?
Basically what I see from this code is:
An axios instance was created and the baseURL was set to being http://localhost:3000/admin
From first glance I can tell that all Api calls that you make a resulting to 404 reason being React locally will always run on port 3000 unless that port is in use.
So now your axios baseURL being to set to port 3000 definitely axios should return a 404 because you surely do not have the endpoint that you are trying to hit
Solution:
Here you are to change the baseURL's port number to the port where Nodejs server is listening on
Then once that is said and done then make sure that even the endpoints that you are trying to hit do exist then your axios calls should work now
Advise:
If the Axios instance created is confusing drop it for a bit and import raw axios not the instance then use axios in it's basic form then once you have that working you surely will have established the correct port number and everything then you can edit the axios instance created with baseURL you have established.
Add
"proxy":"http://localhost:3000/admin"
in your package.json file and restart your React App.
I'm wondering if the following scenario is possible with serverless (deployed on Vercel) Next JS:
I have a route /product/[id].tsx when you send a request with the header Accept: text/html, I would like it to go through the normal Next JS flow with a React page. But, when a request is given with Accept: application/json, I would like it to return the JSON representation.
I have accomplished it by using some custom middleware I wrote for Express (see below), but I would also like to deploy it on Vercel, and Vercel is not designed to work with a custom Express implementation.
So the question is, is it possible to do this in a serverless environment? Could one of the Next JS React pages return pure JSON, or could you return React from a Next JS Api route? Or is there another way to accomplish this?
server.ts
import express, { Request, Response } from "express";
import next from 'next'
import { parse } from 'url'
const port = parseInt(process.env.PORT || "3000", 10);
const dev = process.env.NODE_ENV !== "production";
async function run(): Promise<void> {
const app = express();
const nextApp = next({ dev })
const nextHandler = nextApp.getRequestHandler()
await nextApp.prepare()
app.use((req, res) => {
res.format({
"text/html": async (req, res) => {
const parsedUrl = parse(req.url!, true)
await this.handler(req, res, parsedUrl)
},
"application/json": async (req, res) => {
res.json({
"dummy": "data"
})
}
})
});
app.listen(port, () => {
console.log(
`> Server listening at http://localhost:${port} as ${
dev ? "development" : process.env.NODE_ENV
}`
);
});
}
run()
Thanks to #timneutkens (Lead engineer for NextJS) on Twitter we have our answer. It is possible. You use getServerSideProps and perform everything you want to the res object there. Just be sure to end it with .end so it skips the react rendering phase.
I've made a sample repo for the code here: https://github.com/jaxoncreed/nextjs-content-type-test
And here's the page that matters:
import Head from 'next/head'
export default function Home() {
return (
<div className="container">
<Head>
<title>Create Next App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>Test App</h1>
</main>
</div>
)
}
export async function getServerSideProps({ req, res }) {
if (req.headers.accept === "application/json") {
res.setHeader('Content-Type', 'application/json')
res.write(JSON.stringify({ "dummy": "data" }))
res.end()
}
return {
props: {}, // will be passed to the page component as props
}
}
I'm new to node and react and I am trying to fetch some data and show it on my react page. It's pretty simple. I have an express server running on localhost:3001 and my react app is on localhost:3000.
I'm attempting to fetch data and then set that data to a state via a hook. I can't seem to get the data on the react page or in the web developer console. Is there a way I can see the data that is being fetched in the console?
Here is my React component:
import React, { useState } from "react";
function App() {
const [weatherData, setWeatherData] = useState("");
console.log(weatherData);
React.useEffect(() => {
const fetchData = async () => {
const result = await fetch(
"http://localhost:3001"
);
const data = await result.json();
console.log("data", data);
setWeatherData(data);
};
fetchData();
})
return (
<div>
<h1>The temprature is {weatherData}</h1>
</div>
);
}
export default App;
Here is my node server:
const express = require('express');
const bodyParser = require('body-parser');
const https = require("https");
const cors = require('cors');
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine', 'jsx')
app.use(express.static("public"));
app.get("/", function (req, res) {
const query = Chicago;
const apiKey = "a valid key";
const unit = "imperial";
const url = "https://api.openweathermap.org/data/2.5/weather?appid=" + apiKey + "&q=" + query + "&units=" + unit;
https.get(url, (response) => {
console.log("statusCode", res.statusCode);
response.on("data", (d) => {
const weatherData = (JSON.parse(d));
console.log(weatherData);
res.send(weatherData);
});
}).on("error", (e) => {
console.error(e);
})
});
const port = process.env.PORT || 3001;
app.listen(port, () => console.log(`Listening on port ${port}`));
The result I get is no data and these 2 errors in chrome dev tools console.
index.js:1 Uncaught SyntaxError: Unexpected token '<'
App.jsx:19 Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0
Any help is greatly appreciated!!
You need to specify the Content-Type of data returned from the server.
Either you can use res.setHeader before sending response or res.json() to send json response
https.get(url, (response) => {
console.log("statusCode", res.statusCode);
response.on("data", (d) => {
const weatherData = JSON.parse(d);
console.log(weatherData);
res.json(weatherData); // use json response
});
}).on("error", (e) => {
console.error(e);
})
It seems there maybe an error occur in result.json().
Probably the request return html rather then json.
You can use postman or other tools to get the real response by 'localhost:3001' to clear where goes wrong.
const data = await result.json();
this line will occur a problem if result does not have valid json.
that's why you have encountered an error. catch that error and see the response
Following this quick guide (React and PostgreSQL), the following app should print the JSON fetch to the bash terminal (at ~37min of video).
However this does not happen. There is no feedback on the npm or nodemon servers.
When adding a value via the front-end, firefox instantly sends back a 404 status (observed in console:network). In chrome, the thread hangs as pending until the nodemon server is shut down (and then fails with a connection reset error)(again in console:network).
npm is running app and nodemon is running the server.
app.js
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super();
this.state = {
title: 'Simple postgres app',
treatments: []
}
}
componentDidMount(){
console.log('COMPONENT HAS MOUNTED')
}
addStuff(event){
event.preventDefault()
// console.log('in method');
let data = {
test_field: this.refs.test_field.value,
};
var request = new Request('http://localhost:3000/api/new-thing', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
body: JSON.stringify(data),
message: console.log('JSON output: ', JSON.stringify(data))
});
fetch(request)
.then((response) => {
response.json()
.then((data) => {
console.log(data)
})
})
}
render() {
let title = this.state.title;
return (
<div className="App">
<h1> { title } </h1>
<form ref = "testForm">
<input type="text" ref="test_field" placeholder="test_field"/>
<button onClick={this.addStuff.bind(this)}>Add This</button>
</form>
</div>
);
}
}
export default App;
server.js
let express = require('express');
let bodyParser = require('body-parser');
let morgan = require('morgan');
let pg = require('pg');
const PORT = 3000;
// let pool = new pg.Pool({
// port: 5432,
// user: 'postgres',
// password: 'postgres',
// database: 'po1dev_v0.0.1',
// max: 10, //max connections
// host: 'localhost'
// })
let app = express();
app.use(bodyParser.json);
app.use(bodyParser.urlencoded({ extended:true }));
app.use(morgan('dev'));
app.use((request, response, next) => {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
// app.post('/api/new-thing', (request,response) => {
// console.log(request.body)
// })
app.post('/api/new-thing', function(request,response){
console.log(request.body);
})
app.listen(PORT, () => console.log('Listening on port ' + PORT));
Any ideas on what may be causing the 404/hang problems in firefox/chrome and how to go about fixing it?
Cheers
That's because the route you're creating doen't return any response so it waits indefinitely for a response then gets timed out.
The route should return some response,
app.post('/api/new-thing', function(request,response){
console.log(request.body);
return response.json({"data": "Hello World"})
})
Which will return the {"data": "Hello World"} from the /api/new-thing route.
Also, bodyParser.json is a function not property. Change it to
app.use(bodyParser.json())
If you are using create-react-app try another port for the backend server. Because by default it uses 3000 for the react app.