Upload form data with file using Multer & MYSQL in NextJs
The previous tutorial taught you how to create API endpoints to filter MYSQL database, pagination, and protect the API using middleware. This tutorial teaches you how to upload user form data with image file using Multer and delete data from MYSQL database.
From the project folder, execute the following command to install Multer:
npm install multer
Save the project. Make sure MYSQL is running. You are able to add new user with and image file, updating existing users, and delete users from the database.
Demo Video
From the project folder, execute the following command to install Multer:
npm install multer
Then open api/user/[slug].js file to add post, put, and delete routes. To use multer to parse request body and upload files, you need to disable default body parser by setting bodyParser to false. We use diskStorage() function from multer to configure local storage and file name. Multer needs file field and max count of the file to be uploaded. In this project, we use array() function to specify file field (photo) and the max count of the file to be uploaded (1). We can access text parts of a submitted form data by extracting body from the request. To access file parts, you need to extract files from the request.
.....
import bcrypt from 'bcryptjs';
import multer from 'multer';
import path from 'path';
export const config = {
api: {
bodyParser: false, // disable default body parse
},
};
// storage configuration
var storage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, './public');
},
filename: function (req, file, callback) {
callback(null, file.fieldname + '-' +
Date.now()+ path.extname(file.originalname));
}
});
/* - return Multer instance to process multipart/form-data
-specify file field and max count of file to be uploaded
*/
var upload = multer({ storage : storage }).array('photo',1);
const handler = nextConnect()
.use(middleware)
.post(async (req, res) => { // handle post route
upload(req,res,async function(err) {
if(err) {
return res.end("Error uploading file."+err);
}
const {body} = req; // extract text parts from submitted form data
const {username,email,password}=body;
let password_encrypted= await bcrypt.hash(password, 10);
const f = req.files && req.files[0]?{photo:req.files[0].filename}:null;
const newUser = db.User.create(
{
username:username,
email:email,
password: password_encrypted,
...f,
},
);
return res.status(200).json({
status: 'success',
message: 'done',
data: newUser,
});
})
.put(async (req, res) => { // handle put route
upload(req,res,async function(err) {
if(err) {
return res.end("Error uploading file."+err);
}
const {body} = req;
const {username,email, password}=body;
const p = password && password!=='undefined'?
{ password:await bcrypt.hash(password, 10)}:null;
const f = req.files && req.files[0]?{photo:req.files[0].filename}:null;
const updateFields = {username:username,email:email,...p,...f};
const updatedRows = db.User.update(
updateFields,
{
where: { id: body.id },
}
);
return res.status(200).json({
status: 'success',
message: 'done',
data: updatedRows,
});
})
.delete(async (req, res) => { // handle delete route
const { slug } = req.query;
const userId = slug;
await db.User.destroy({
where: { id: userId },
});
res.status(200).send({
message:
"Deleted"
});
})
............
Now our API endpoints are ready. Let modify page/user/list.js file to access the APIs. The url /api/user/upload and /api/user/${id} are handled by /api/user/[slug] route. If the form is in edit mode, the /api/user/upload is handled by put method of the API endpoint. Otherwise, it is handled by post method. The /api/user/${id} matches to the delete method.
import React,{ useState, useEffect } from "react"; import axios from "axios"; import { FaTrash } from "react-icons/fa" import { FaEdit } from "react-icons/fa" import { BsFilterCircle,BsFileEarmarkExcel,BsPlus } from "react-icons/bs" import * as FileSaver from "file-saver"; import * as XLSX from "xlsx"; import { Label,Form,FormGroup,Input, Button,Col,Table, Modal, ModalHeader, ModalBody, ModalFooter, Popover, PopoverHeader, PopoverBody, } from "reactstrap"; import {getAppCookies } from "../../middleware/utils"; import Layout from "../../components/layout"; import Router from 'next/router'; // global variables var start=0; var step=4; function UserList(props) { //vairables/ const {token } = props; const [users,setUsers]=useState(null); const [modal,setModal]=useState(false); const [popover,setsetPopover]=useState(false); const [inputs, setInputs] = useState({}); const [id,setId]=useState(0); const [refresh,setRefresh]=useState(false); const [sorted,setSorted]=useState(false); const [isEditMode,setIsEditmode]=useState(false); const [numRows,setNumRows]=useState(0); const hstyle={textDecoration: 'none'}; const [file,setFile]=useState(null); const pstyle={ marginRight: '20px', textDecoration: 'none', }; // fetch data from server using Axios useEffect(() => {
if(token){ axios .get(`/api/user/list?name=${inputs.name}&email=${inputs.email_addr}&fromid=${inputs.fromid}&toid=${inputs.toid}&start=${start}&limit=${step}`,{ headers:{ "Content-type":"application/json", "authorization": `Bearer ${token}`, }, }) .then((res) => { parseResult(res.data); }) .catch((err) => console.log(err));
}
else{
Router.push('/user/login'); // redirect to login page
}
}, [refresh]); async function handleSubmit(e) { e.preventDefault(); let form_data = new FormData(); form_data.append('id', id); form_data.append('username', inputs.username); form_data.append('email', inputs.email); form_data.append('password', inputs.password); if(file!=null){ form_data.append('photo', file,file.name); } for (var key of form_data.entries()) { console.log(key[0] + ', ' + key[1]); } axios({ method: isEditMode?'put':'post', url: `/api/user/upload`, data: form_data, headers: { "authorization": `Bearer ${token}` } }).then(response=>{ let resobj=JSON.parse(JSON.stringify(response.data)); if(resobj.status==='success'){ setRefresh(!refresh); setFile(null); toggle(); } }); }; const searchUser= () =>{ start=0; // reset start axios({ method: 'get', url: `/api/user/list?name=${inputs.name}&email=${inputs.email_addr}&fromid=${inputs.fromid}&toid=${inputs.toid}&start=${start}&limit=${step}`, headers: { "Content-type":"application/json","authorization": `Bearer ${token}`} }).then(res=>{ parseResult(res.data); }); }; const deleteUser = (id) => { if(confirm("Are you sure to delete?")) { axios({ method: 'delete', url: `/api/user/${id}/`, headers: { "Content-type":"application/json","authorization": `Bearer ${token}`} }).then(response=>{ console.log("deleted",response.data); setRefresh(!refresh); }); } }; const editUser = (item) => { setIsEditmode(true); setId(item.id); setInputs(values => ({...values, ['username']: item.username})); setInputs(values => ({...values, ['email']: item.email})); toggle(!modal); }; function toggle() { if(!modal) setFile(null); setModal(!modal); } function togglePop() { setsetPopover(!popover); } function openAddNewModel() { setIsEditmode(false); toggle(); } const handleImageChange = (e) => { setFile(e.target.files[0]); }; const handleChange = (event) =>{ const name = event.target.name; const value = event.target.value; setInputs(values => ({...values, [name]: value})) }; const handlePaging = (mv) =>{ if(mv==='next') { if(start<numRows-step) start=start+step; } else if(mv==='prev') { if(start>=step) start=start-step; } axios({ method: 'get', url: `/api/user/list?name=${inputs.name}&email=${inputs.email_addr}&fromid=${inputs.fromid}&toid=${inputs.toid}&start=${start}&limit=${step}`, headers: { "Content-type":"application/json","authorization": `Bearer ${token}`} }).then(res=>{ parseResult(res.data); }); } const handleHeaderClick = (field) =>{ // sort records if(field!=='id') users.sort((a,b) => a[field].localeCompare(b[field])); else users.sort((a,b) => b[field]-a[field]); // refresh UI setSorted(!sorted); } const parseResult = (data) =>{ let dt=JSON.parse(JSON.stringify(data)); if(dt.message==='success'){ setUsers(dt.data); setNumRows(dt.numrow); } } // export to excel const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"; const fileExtension = ".xlsx"; const exportToCSV = (apiData, fileName) => { const ws = XLSX.utils.json_to_sheet(apiData); const wb = { Sheets: { data: ws }, SheetNames: ["data"] }; const excelBuffer = XLSX.write(wb, { bookType: "xlsx", type: "array" }); const data = new Blob([excelBuffer], { type: fileType }); FileSaver.saveAs(data, fileName + fileExtension); }; // return ( <Layout token={token}
title='User Admin Panel' > <div className="container"> <div style={{float: 'right'}}> <Button color="primary" id="btfilter"><BsFilterCircle/></Button> </div> <Popover placement="top" isOpen={popover} target="btfilter" toggle={togglePop}> <PopoverHeader>Filter</PopoverHeader> <PopoverBody> <FormGroup row> <Col sm={10}> <Input type="text" value={inputs.name || ""} name="name" id="name" placeholder="By User name" bsSize="lg" onChange={handleChange}/> </Col> </FormGroup> <FormGroup row> <Col sm={10}> <Input type="text" value={inputs.email_addr || ""} name="email_addr" id="email_addr" placeholder="By Email" onChange={(e) =>handleChange(e)} /> </Col> </FormGroup> <FormGroup row> <Col sm={6}> <Input type="number" value={inputs.fromid || ""} name="fromid" id="fromid" placeholder="From ID" onChange={(e) =>handleChange(e)} /> </Col> <Col sm={6}> <Input type="number" value={inputs.toid || ""} name="toid" id="toid" placeholder="To ID" onChange={(e) =>handleChange(e)} /> </Col> </FormGroup> <Button color="primary" onClick={(e) =>searchUser()}>Ok</Button> </PopoverBody> </Popover> <div style={{float: 'right',marginRight: '10px'}}> <Button color="primary" onClick={(e) => exportToCSV(users, 'users')}><BsFileEarmarkExcel/></Button> </div> <div>Users List ({numRows} items)</div> { <Table striped bordered hover> <thead> <tr> <th> <a style={hstyle} href="#" onClick={() => handleHeaderClick("id")}>ID</a></th> <th > <a style={hstyle} href="#" onClick={() => handleHeaderClick("username")}>User name</a></th> <th><a style={hstyle} href="#" onClick={() => handleHeaderClick("email")}>Email</a></th> <th colSpan={3}></th> </tr> </thead> <tbody> { users && users.map((item, i) => { return ( <tr key={item.id}> <td >{item.id}</td> <td >{item.username}</td> <td >{item.email}</td> <td><img style={{width: '50px',height:'50px'}} src={"/"+item.photo}/></td> <td > <Button color="primary" onClick={() =>editUser(item)}> <FaEdit style={{ color: "white", fontSize: "12px" }} /> </Button></td> <td > <Button color="primary" onClick={() =>deleteUser(item.id)}> <FaTrash style={{ color: "red", fontSize: "12px" }} /> </Button></td> </tr> ); }) } </tbody> </Table> } { (start-step>=0 && numRows>step) && <a href="#" onClick={() =>handlePaging('prev')} style={pstyle} to={"#"}>{"<"}</a> } { (numRows-start>=step && numRows>step) && <a href="#" onClick={() =>handlePaging('next')} style={pstyle} to={"#"}>{">"}</a> } <div style={{float: 'right'}}> <Button color="primary" onClick={openAddNewModel}><BsPlus/></Button> </div> <Modal isOpen={modal} toggle={toggle}> <ModalHeader toggle={toggle}>{isEditMode?'Edit User':'Add User'}</ModalHeader> <ModalBody> <Form> <FormGroup row> <Label for="username" sm={3} size="lg">Username</Label> <Col sm={8}> <Input type="text" value={inputs.username || ""} name="username" id="username" placeholder="User name" bsSize="lg" onChange={(e) =>handleChange(e)} required /> </Col> </FormGroup> <FormGroup row> <Label for="email" sm={3}>Email</Label> <Col sm={8}> <Input type="text" value={inputs.email || ""} name="email" id="email" placeholder="Email" onChange={(e) =>handleChange(e)} required /> </Col> </FormGroup> <FormGroup row> <Label for="password" sm={3}>Password</Label> <Col sm={8}> <Input type="password" value={inputs.password || ""} name="password" id="password" placeholder="Password" onChange={(e) =>handleChange(e)} required /> </Col> </FormGroup> <FormGroup> <p><Label for="file">File</Label> <Input type="file" id="file" name="file" accept="image/png, image/jpeg" onChange={handleImageChange} /> </p> </FormGroup> </Form> </ModalBody> <ModalFooter> <Button color="primary" onClick={handleSubmit}>Ok</Button> <Button color="secondary" onClick={toggle}>Cancel</Button> </ModalFooter> </Modal> </div> </Layout> ); } /* getServerSideProps */ export async function getServerSideProps(context) { const { req } = context; const token = getAppCookies(req).token || ''; return { props: { token, }, }; } export default UserList;
Save the project. Make sure MYSQL is running. You are able to add new user with and image file, updating existing users, and delete users from the database.
Demo Video
Comments
Post a Comment