for several days I have problem uploading an image with Laravel api. I was sending request via React’s modified axios. Code is as follows.
axios-client.js
import axios from "axios";
const axiosClient = axios.create({
baseURL : `${import.meta.env.VITE_API_BASE_URL}/api`
});
axiosClient.interceptors.request.use((config) => {
const token = localStorage.getItem('ACCESS_TOKEN');
config.headers.Authorization = `Bearer ${token}`;
if (config.data instanceof FormData) {
console.log('Form Data accepted')
config.headers['Content-Type'] = 'multipart/form-data';
}
return config;
});
axiosClient.interceptors.response.use((response) => {
return response;
}, (error) => {
try {
const { response } = error;
if (response.status === 401) {
localStorage.removeItem('ACCESS_TOKEN');
}
} catch (e) {
console.log(e);
}
throw error;
});
export default axiosClient;
React’s component
BrandForm.jsx
import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import axiosClient from '../../axios-client';
function BrandForm() {
const {id} = useParams();
const navigate = useNavigate();
const [loading,setLoading] = useState(false);
const [errors, setErrors] = useState(null);
const [brand,setBrand] = useState({
id: null,
name: '',
logo: null
});
const config = {headers: {'Content-Type': 'multipart/form-data'}}
if (id){
useEffect(()=>{
setLoading(true);
axiosClient.get(`/car-make/${id}`)
.then(({data})=>{
console.log(data)
setBrand(data);
setLoading(false)
})
.catch((e)=>{
console.log(e)
setLoading(false);
})
},[]);
}
const onSubmit = (e)=>{
e.preventDefault();
setErrors(null);
if (brand.id){
axiosClient.put(`/car-make/${brand.id}`, brand, config)
.then(({data})=>{
//TODO show notification
console.log('response update', data)
navigate(path);
})
.catch( (err) => {
console.log(err)
const response = err.response;
if(response && response.status ===422){
setErrors(response.data.errors);
}
});
}
else {
axiosClient.post(`/car-make`, brand, config)
.then(()=>{
//TODO show notification
console.log('response create', data)
navigate(path);
})
.catch( (err) => {
const response = err.response;
if(response && response.status ===422){
setErrors(response.data.errors);
}
});
}
}
return (
<div>
{brand?.id ? <h1>Edit Brand: {brand.name}</h1> : <h1>New Brand</h1>}
<div className="card animated fadeInDown">
{loading && <div className="text-center">Loading...</div>}
{errors && (
<div className="alert">
{Object.keys(errors).map((key) => (
<p key={key}>{errors[key][0]}</p>
))}
</div>
)}
{!loading && (
<form onSubmit={onSubmit}>
<input
value={brand?.name}
onChange={(e) => setBrand({ ...brand, name: e.target.value })}
type="text"
placeholder="Name"
/>
<input
onChange={(e) => setBrand({ ...brand, logo: e.target.files[0] })}
type="file"
placeholder="Logo"
/>
<button className="btn" style={{ marginTop: '20px' }}>Save</button>
</form>
)}
</div>
</div>
);
}
export default BrandForm;
Laravel’s
api.php
Route::middleware('auth:sanctum')->group(function(){
Route::put('/transfer/execute/{id}', [TransferController::class,'execute']);
Route::put('/transfer/cancel/{id}', [TransferController::class,'cancel']);
Route::apiResource('/transfer', TransferController::class);
Route::apiResource('/vehicle', VehicleController::class);
Route::apiResource('/car-make', CarMakeController::class);
Route::apiResource('/car-model', CarModelController::class);
Route::get('/companies/partner',[CompanyController::class,'indexPartner']);
Route::get('/companies/fleet',[CompanyController::class,'indexFlota']);
Route::apiResource('/companies', CompanyController::class);
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::post('/logout', [AuthController::class,'logout']);
Route::apiResource('/users', UserController::class);
});
Route::post('/signup', [AuthController::class,'signup']);
Route::post('/login', [AuthController::class,'login']);
Model
namespace AppModels;
use IlluminateDatabaseEloquentFactoriesHasFactory;
use IlluminateDatabaseEloquentModel;
class CarMake extends Model
{
use HasFactory;
protected $fillable = ['name', 'logo'];
protected $primarykey = "id";
public function carModels()
{
return $this->hasMany(CarModel::class,'make');
}
}
Controller
.
.
.
public function update(UpdateCarMakeRequest $request, CarMake $carMake)
{
Log::info('Update method called');
Log::info('Update Brand Request - Raw Request Data:', $request->all());
$data = $request->validated();
if ($request->hasFile('logo')) {
$file = $request->file('logo');
$fileName = time() . '_' . $file->getClientOriginalName();
$filePath = $file->storeAs('logo', $fileName, 'public');
$data['logo'] = $filePath;
} else {
unset($data['logo']);
}
Log::info('Update Brand Request - Validated Data:', $data);
$carMake->update($data);
return new CarMakeResource($carMake);
}
.
.
.
Update Request
class UpdateCarMakeRequest extends FormRequest
{
public function authorize()
{
return true; // Adjust as needed
}
public function rules()
{
return [
'name' => 'required|string|max:255',
'logo' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
];
}
protected function failedValidation(Validator $validator)
{
Log::error('Validation errors:', $validator->errors()->toArray());
throw new HttpResponseException(response()->json([
'errors' => $validator->errors()
], 422));
}
}
Reource
class CarMakeResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id'=> $this->id,
'name' => $this->name,
'logo'=> $this->logo,
];
}
}
The Log file looks like this after trying to edit
[2024-07-18 05:29:50] local.ERROR: Validation errors: {"name":["The name field is required."]}
[2024-07-18 05:44:36] local.ERROR: Validation errors: {"name":["The name field is required."]}
Note: when I try updating CarMake without an image and without config parameter in axios
request it works fine
Thanks in advance for your effort!
2
Answers
Your request interceptor data handling is completely redundant. Axios already handles
FormData
payloads and does so correctly. Yours is missing the mime boundary token.Remove this and let Axios and your browser set the appropriate content-type header
You also cannot embed files in a JSON document. Create a
FormData
instance for your request payload, populate the data and send thatFor Laravel to handle
multipart/form-data
PUT requests, you might need to resort toaxios.post()
and append_method=PUT
See PATCH and PUT Request Does not Working with form-data
Method
validated()
use for retrieving validated input.Within your controller, instead of
validated()
method, use$request->validate()
So, your controller should be look likes
You can refer to docs for more information