I'm new to node.js and react. I've built a back end api to expose CRUD endpoints for a Products model with three fields: Title, Description and Price. I've used node/express for the server with an SQLite db file.
I've tested the endpoints using postman and can successfully access all the products, retrieve a single product, add a new product (but only with the header set to application/x-www-form-urlencoded), and delete a product.
I'm loosely following a tutorial building the front end with react and using axios to make http requests. I can read without issue so get all and get one by ID is working.
However, I've hit a wall with my post request. If I send a post request I get this error backend in the api server console -
Unhandled rejection SequelizeValidationError: notNull Violation: product
And on the front end after about a couple of minutes from sending the request I get:
XHR failed loading: POST "http://localhost:3000/api/products/new".
dispatchXhrRequest # xhr.js:178
xhrAdapter # xhr.js:12
dispatchRequest # dispatchRequest.js:59
Promise resolved (async)
request # Axios.js:51
Axios.(anonymous function) # Axios.js:71
wrap # bind.js:9
NewProduct._this.createHandler # ProductNew.js:25
callCallback # react-dom.development.js:542
invokeGuardedCallbackDev # react-dom.development.js:581
invokeGuardedCallback # react-dom.development.js:438
invokeGuardedCallbackAndCatchFirstError # react-dom.development.js:452
executeDispatch # react-dom.development.js:836
executeDispatchesInOrder # react-dom.development.js:858
executeDispatchesAndRelease # react-dom.development.js:956
executeDispatchesAndReleaseTopLevel # react-dom.development.js:967
forEachAccumulated # react-dom.development.js:935
processEventQueue # react-dom.development.js:1112
runEventQueueInBatch # react-dom.development.js:3607
handleTopLevel # react-dom.development.js:3616
handleTopLevelImpl # react-dom.development.js:3347
batchedUpdates # react-dom.development.js:11082
batchedUpdates # react-dom.development.js:2330
dispatchEvent # react-dom.development.js:3421
09:59:59.293 ProductNew.js:30
Error: Network Error
at createError (createError.js:16)
at XMLHttpRequest.handleError (xhr.js:87)
If I remove the validation a new product is successfully added to the database, but all the values are null.
This is my ProductNew.js file:
import React from 'react';
import axios from'axios';
import ButtonAdd from '../components/Buttons/ButtonAdd';
class NewProduct extends React.Component {
state = {
title: '',
description: '',
price: ''
}
createHandler = () => {
const data = {
Title: this.state.title,
Description: this.state.description,
Price: this.state.price
};
console.log('Raw data is: ' + Object.entries(data));
// => Raw data is: Title,burger,Description,bun,Price,5
const header = {
ContentType: 'application/x-www-form-urlencoded',
Accept: 'application/json'
};
const querystring = require('querystring');
console.log(querystring.stringify(data));
// => Title=burger&Description=bun&Price=5
console.log('Data is:' + JSON.stringify(data));
// => Data is:{"Title":"burger","Description":"bun","Price":"5"}
axios.post('/api/products/new', querystring.stringify(data), header)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
}
render () {
return (
<div className='NewPost'>
<h1>Add a Product</h1>
<label>Title</label>
<textarea rows='4' value={this.state.title} onChange={(event) => this.setState({title: event.target.value})} />
<label>Description</label>
<textarea rows='6' value={this.state.description} onChange={(event) => this.setState({description: event.target.value})} />
<label>Price</label>
<input type='number' value={this.state.price} onChange={(event) => this.setState({price: event.target.value})} />
// ButtonAdd.js onClick={props.create} added to <Button> tag
<ButtonAdd create={this.createHandler}>Add New Product</ButtonAdd>
</div>
);
}
}
export default NewProduct;
I found this issue on github which I hoped would have solved my problem. I've added various console.log to follow what is happening with the state data through the script but I can't see why my POST request is empty. I'd appreciate any pointers as to what I'm doing wrong.
Maybe it will help you somehow. I think you have a problem with your axios request structure.
Docs say: axios.post(url[, data[, config]])
Instead of putting headers directly, as the last parameter:
axios.post('/api/products/new', querystring.stringify(data), header)
you should have a config object that contains headers and then pass it as the last parameter:
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
}
};
axios.post('/api/products/new', querystring.stringify(data), config)
It appears that you may be using an incorrect Content type. The proper content type should be application/json.
The following answer has an example of the differing formats that are passed by the various content types, which may be contributing to your issues: differences in application/json and application/x-www-form-urlencoded
Related
I'm a beginner coder and I'm trying to create an application using React as my front end and Ruby on Rails for my back end. Anytime I press the sign up button and send the request to server I'm receiving an error and I cannot figure out what it is. Help!
Console:
SignUp.js:17
POST http://localhost:4000/users 500 (Internal Server Error)
SignUp.js:26
Response {type: 'basic', url: 'http://localhost:4000/users', redirected: false, status: 500, ok: false, …} body: (...) body Used: true headers: Headers {} ok: false redirected: false status: 500 statusText: "Internal Server Error" type: "basic" url: "http://localhost:4000/users" [[Prototype]]: Response
When trying to access localhost
GET http://localhost:3000/me 500 (Internal Server Error)
favicon.ico:1 GET http://localhost:3000/favicon.ico 500 (Internal Server Error)
Rails Server:
ArgumentError (wrong number of arguments (given 0, expected 1..2)):
app/controllers/users_controller.rb:2:in `<class:UsersController>'
app/controllers/users_controller.rb:1:in `<main>'
Started POST "/users" for 127.0.0.1 at 2022-11-02 23:09:07 -0400
ArgumentError (wrong number of arguments (given 0, expected 1..2)):
app/controllers/users_controller.rb:2:in `<class:UsersController>'
app/controllers/users_controller.rb:1:in `<main>'
React front end
import React from 'react';
import { useState } from "react";
function SignUp() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
function handleSignUp(e) {
e.preventDefault()
const user = {
username,
password
}
fetch("/users",{
method: "POST",
header: {
"Content-Type" : "application/json"
},
body: JSON.stringify(user)
}).then(r => {
r.json()
console.log(r)})
}
return (
<div>
<form onSubmit={handleSignUp}>
<p>Username</p>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<p>Password</p>
<input
type="text"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">Sign Up</button>
</form>
</div>
);
}
export default SignUp;
Routes
Rails.application.routes.draw do
# Routing logic: fallback requests for React Router.
# Leave this here to help deploy your app later!
post "/login", to: "sessions#create"
delete "logout", to: "sessions#destroy"
get "/me", to: "users#show"
post "/users", to: "users#create"
get "*path", to: "fallback#index", constraints: ->(req) { !req.xhr? && req.format.html? }
end
Controller
class UsersController < ApplicationController
wrap_parameters
rescue_from ActiveRecord:RecordInvalid, with: :record_invalid_response
def create
user = User.create!(user_params)
render json: user, status: :created
rescue ActiveRecord::RecordInvalid => invalid
end
def show
user = User.find(session[:user_id])
if user
render json: user
else
render json: { error: "Not authorized" }, status: :unauthorized
end
end
private
def user_params
params.permit(:username, :password)
# :first_name, :last_name, :phone_number, :email
end
def record_invalid_response(user)
render json: {error: user.errors.full_messages}, status: :unprocessable_entity
end
end
:The error comes from the fact that wrap_parameters expects some arguments, see https://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html.
Now, for a JSON request, you don't need to call this function as Rails does it by default. But then I suppose Rails wraps your params with a user key and so you need to change your params processing, something like this should work:
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordInvalid, with: :record_invalid_response
def create
user = User.create!(user_params)
render json: user, status: :created
end
def show
user = User.find(session[:user_id])
if user
render json: user
else
render json: { error: "Not authorized" }, status: :unauthorized
end
end
private
def user_params
params.require(:user).permit(:username, :password)
# :first_name, :last_name, :phone_number, :email
end
def record_invalid_response(user)
render json: {error: user.errors.full_messages}, status: :unprocessable_entity
end
end
Note: I also removed the rescue from the create action as it would prevent your controller-level rescue_from from being used
I am trying to upload an image from React using rails active storage. The problem is I am receiving the following error when making the request,
ArgumentError (Could not find or build blob: expected attachable, got <ActionController::Parameters {} permitted: false>):
The controller with the create action.
def create
micropost = #current_user.microposts.build(micropost_params)
micropost.image.attach(params[:micropost][:image])
binding.pry
if micropost.save
render json: micropost
else
render json: { errors: micropost.errors }
end
end
I am using strong parameters.
def micropost_params
params.require(:micropost).permit(:content, :image)
end
This is how the form looks like in the frontend
<input
type="file"
accept="image/*"
multiple={false}
onChange={(event) => setImage(event.target.files[0])}
/>
The request I make in the frontend.
export async function createMicropost(content, image) {
console.log(image);
return request({
url: "/micropost/new",
method: "POST",
data: {
micropost: {
image: image,
content: content,
},
},
});
}
I have also tried replacing :image with image: {} in micropost_params, it stopped rendering the Unpermitted parameter: :image error when I used binding.pry but the 500 still remained with the same error as in the first image.
My web app information:
Frontend: React.js
Backend: Flask
Goal: User enters in zip codes and the backend returns the zip code coordinates and distance on the web page
Problem: I was initially under the impression that you only use POST requests when inserting into a database, so I was defining my API endpoints as GET methods. However, since I am under the assumption that it is best to not pass body parameters for GET methods, my current solution is to have it as a POST request. Is this the best practice? Or is there a better way to do it?
Frontend Code
const handleSubmit = e => {
const formData = { "origin": originInput, "destination": destinationInput
}
e.preventDefault();
checkError();
fetch('/path', {
credentials: 'include',
method: 'POST',
body: JSON.stringify(formData),
headers: { 'content-type': 'application/json' }
}).then(res => console.log(res))
.catch(err => console.log(err));
};
return (
<div className="card">
<form onSubmit={handleSubmit}>
<section>
<label htmlFor="origin">Start Zip</label>
<input
type="text"
name="origin"
id="origin"
onChange={e => handleChange(e, updateOriginInput)}
/>
</section>
<section>
<label htmlFor="destination">End Zip</label>
<input
type="text"
name="destination"
id="destination"
onChange={e => handleChange(e, updateDestinationInput)}
/>
</section>
<section>
<input type="submit" value="Get Directions" />
</section>
</form>
</div>
);
Backend Code
#path.route('/path', methods=['POST'])
def path_info():
request_data = request.get_json()
ORIGIN = request_data['origin']
DESTINATION = request_data['destination']
# The df var is pd.DataFrame(zip([ORIGIN, get_lon(ORIGIN), get_lat(ORIGIN)], [DESTINATION, get_lon(DESTINATION), get_lat(DESTINATION)])).T
df, MILES = preprocess(ORIGIN, DESTINATION)
print(df)
return {
'origin': {
'longitude': df['LON'][0],
'latitude': df['LAT'][0]
},
'destination': {
'longitude': df['LON'][1],
'latitude': df['LAT'][1]
},
'miles': MILES
}
Thanks in advance!
I was initially under the impression that you only use POST requests when inserting into a database, so I was defining my API endpoints as GET methods. However, since I am under the assumption that it is best to not pass body parameters for GET methods, my current solution is to have it as a POST request. Is this the best practice? Or is there a better way to do it?
Today: if you need to send information in the body of the HTTP request, then you should expect to use POST (with PUT/PATCH available in specific cases).
If you want to instead use GET, then you need to take the information collected by the form and figure out how to encode it into the request URI itself. On the web, that usually takes the form of key value pairs that have been application/x-www-form-urlcoded and copied into the query part of the request URI. It doesn't have to be in the query part - URI Templates can be used to describe the expansion of URI paths as well.
There is an internet draft to standardize a new HTTP method (QUERY) which would provide semantics for a safe request with a message body; but that method hasn't been standardized or registered yet.
I'm trying to upload and image using Bootstrap-Vue Form File Input and send it to Flask Backend via POST using Axios library, then store in a folder.
My problem is that Flask can't find "file" in "request.files". Pretty sure I'm falling in a rookie mistake.
That's my code:
Frontend:
<template>
<div class="mx-5 container-fluid">
<div class="mx-5 row">
<div class="col-sm-10">
<b-form-file
type="file"
id="file"
v-model="file"
:state="Boolean(file)"
ref="file"
placeholder="Choose a file or drop it here..."
drop-placeholder="Drop file here..."
v-on:change="submitFile"
></b-form-file>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
file: null,
};
},
methods: {
submitFile() {
/* Initialize the form data */
const path = 'http://localhost:5000/single-file';
const formData = new FormData();
/* Add the form data we need to submit */
formData.append('file', this.file);
/* Make the request to the POST /single-file URL */
axios.post(path,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
},
}).then(() => {
// console.log('SUCCESS!!');
})
.catch(() => {
// console.log('FAILURE!!');
});
},
},
};
Backend:
from flask import Flask, jsonify, request, send_file, redirect, url_for
from werkzeug.utils import secure_filename
import os
# configuration
DEBUG = True
UPLOAD_FOLDER = '/images'
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
#app.route('/single-file', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
# check if the post request has the file part
if 'file' not in request.files:
print('No file part')
return redirect(request.url)
file = request.files['file']
# If the user does not select a file, the browser submits an
# empty file without a filename.
if file.filename == '':
print('No selected file')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('download_file', name=filename))
return ''
if __name__ == '__main__':
app.run()
I get HTTP code 302 (redirect) and print in console 'No file part'.
Any help would be very apreciated.
I can't see an obvious mistake in your code, it seems that the request is correctly passed through from your frontend and backend.
What I would suggest is to use Postman to decouple your frontend and backend in this case. If you get a correct response when using Postman, you can narrow down that the error is in the frontend, the browser, or something about axios which is meddling with the request data.
Also, try to get an error message, or print why flask thinks "file" isnt in request.files, it should be there if everything works as intended
I followed the response for Get the data received in a Flask request to get to the flask documentation for the Request class: Each key in files is the name from the <input type="file" name="">, which means that most likely you have to change 'file' from file = request.files['file'] to point to the actual filename that was selected from the file selection menu.
I am developing a web application with Flask on the backend and React and Redux on the frontend.
I want to add a "Change Profile Picture" option to the profile page but whenever I make a post request with axios to my /api/user/upload_image/ route, i get the following errors:
Access to XMLHttpRequest at 'http://localhost:5000/api/user/update_image' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
PATCH http://localhost:5000/api/user/update_image net::ERR_FAILED
Which is weird becuase I have set up my CORS wrapper in my Flask app like so:
self.cors = CORS(self.app, resources={r"/api/*": {"origins": "*"}})
which should allow requests to /api/ from all origins.
I also tried to do the same thing with Postman and it worked like a charm - uploaded the file and saved it to /server/public/profile_pictures/
When i try to upload regular JSON text from my react application it works as well. It bugs out on file uploads only.
Here is the JSX for the input + the event handler
<label>
Change Profile Picture
<input onChange={(e) => {
this.setState({image: e.target.files[0]})}
} type="file" name="image" />
</label>
Then i have a submit button which dispatches the following action with this.state.image as a parameter:
export const updateImage = (file) => {
return async (dispatch, getState) => {
const formData = {
user_id: getState().currentUser.user.user_id,
auth_key: getState().currentUser.auth_key,
image: file
}
Axios.patch("http://localhost:5000/api/user/update_image", formData, {
headers: {
'Content-Type' : 'multipart/form-data'
}
})
.then(response => {
dispatch({type: UPDATE_IMAGE, payload: response.data})
})
}
I tried using the built in formData method to create the JS object too but that was no good either.
Finally here is the python method which is called when the /api/user/update_image route is hit:
def update_image(self, request):
image = request.files['image']
data = request.params
image.save("./public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg")
fsql.update("""UPDATE users SET profile_picture = %s WHERE user_id = %s""", ("/public/profile_pictures/user_p_picture_id_"+data['user_id']+".jpg", data['user_id']))
return jsonify({
"error_code" : "200",
"error_message" : "Success"
})
I actually solved this about a week and a half ago but I checked the status today.
So the solution was to make a few changes to my config parameter and CORS parameters. Here is the configs i am using right now:
config = {
'ORIGINS': [
'http://localhost:3000', # React
'http://127.0.0.1:3000', # React
],
'SECRET_KEY': '...' #secret key
self.cors = CORS(self.app, resources={
r'/api/*': {
"Access-Control-Allow-Origin": config["ORIGINS"],
"Access-Control-Allow-Credentials": True,
'supports_credentials': True
},
},
supports_credentials = True,
expose_headers = "*"
)
self.app.config['UPLOAD_FOLDER'] = r'/*' # Change this to only the folder you want to save images to
self.app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # Change this according to your file size
This solved my CORS and file transport issues.
I really hope this helps someone. The CORS docs on flask-cors do not cover everything in regards to file uploading and session storage so we kind of have to solve the errors without knowing how everything works - like trying to solve a puzzle with missing pieces.
HMU in messages if you have any good tools for CORS in flask which are well documented and have a community around them.