Pada tutorial ini Anda akan belajar bagaimana membuat CRUD + Image menggunakan node.js, express, MySQL, dan React JS.
Tidak hanya itu,
Pada tutorial ini Anda juga akan belajar menggunakan Bulma CSS untuk style pada frontend.
Dengan demikian, aplikasi yang dibangun menjadi lebih user friendly dengan user interface (UI) yang elegan dan responsif.
Ini bukan tutorial untuk pemula,
Jika Anda seorang pemula di node.js express, saya sarankan Anda terlebih dahulu mempelajari “Tutorial Express Js Untuk Pemula”.
Dan jika Anda pemula di React JS, saya sarankan Anda terlebih dahulu mempelajari “Tutorial React Js Untuk Pemula”.
Mari kita mulai.
#1. Install Express, MySQL2, Nodemon, dan Cors
Buat sebuah folder di komputer Anda, di sini saya beri nama “fullstack”.
Jika Anda membuat folder dengan nama yang sama, itu lebih baik.
Anda bebas membuatnya di manapun, baik di C, D, ataupun di Desktop.
Kemudian buka folder “fullstack” tersebut menggunakan code editor, disini saya menggunakan Visual Studio Code.
Saya juga menyarankan Anda untuk menggunakan Visual Studio Code.
Anda dapat mendownload Visual Studio Code pada link berikut, kemudian install di komputer Anda:
https://code.visualstudio.com/
Setelah folder “fullstack” ter-open menggunakan Visual Studio Code, buat sebuah sub folder bernama “backend” di dalam folder “fullstack”.
Selanjutnya, buka terminal pada Visual Studio Code. Kemudian, masuk ke folder “backend” dengan mengetikan perintah berikut pada terminal:
cd backend
Setelah itu, ketikkan perintah berikut pada terminal untuk membuat file “package.json”:
npm init -y
Selanjutnya, install express, mysql2, sequelize, express-fileupload, dan cors dengan mengetikan perintah berikut pada terminal:
npm install express express-fileupload mysql2 sequelize cors
Selanjutnya, install nodemon secara global dengan mengetikan perintah berikut pada terminal:
npm install -g nodemon
Selanjutnya, tambahkan kode berikut pada file “package.json”:
"type": "module",
Sehingga file “package.json” terlihat menjadi seperti berikut:
{ "name": "backend", "version": "1.0.0", "description": "", "type": "module", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "cors": "^2.8.5", "express": "^4.18.1", "express-fileupload": "^1.3.1", "mysql2": "^2.3.3", "sequelize": "^6.19.1" } }
Hal ini bertujuan agar kita dapat menggunakan ES6 Module Syntax untuk export dan import module.
#2. Buat database
Untuk dapat menggunakan MySQL, Anda perlu menginstall XAMPP, WAMP, MAMP, atau software sejenisnya.
Pada tutorial ini, saya menggunakan XAMPP.
Kemudian buat database baru pada MySQL, Anda dapat menggunakan tools seperti SQLyog, PHPMyAdmin atau sejenisnya.
Disini saya membuat database dengan nama “upload_db”.
Jika Anda membuat database dengan nama yang sama, itu lebih baik.
Untuk membuat database pada MySQL, dapat dilakukan dengan mengeksekusi query berikut:
CREATE DATABASE upload_db;
Perintah SQL diatas akan membuat sebuah database dengan nama “upload_db”.
#3. Struktur Aplikasi
Agar aplikasi lebih terstruktur rapi, kita akan menerapkan pola MVC (Model-View-Controllers).
Buat folder “config”, “controllers”, “models”, “public” dan “routes” di dalam folder “backend”.
Kemudian buat file “Database.js” di dalam folder “config”, buat file “ProductController.js” di dalam folder “controllers”, buat file “ProductModel.js” di dalam folder “models”, buat folder “images” di dalam folder “public”, buat file “ProductRoute.js” di dalam folder “routes”, dan buat file “index.js” di dalam folder “backend”.
Perhatikan gambar berikut untuk lebih jelasnya:
#4. Connect ke Database
Buka file “Database.js” yang terdapat pada folder “config”, kemudian ketikan kode berikut:
import {Sequelize} from "sequelize"; const db = new Sequelize('upload_db','root','',{ host: 'localhost', dialect: 'mysql' }); export default db;
#5. Models
Buka file model “ProductModel.js” yang terdapat pada folder “models”, kemudian ketikan kode berikut:
import {Sequelize} from "sequelize"; import db from "../config/Database.js"; const {DataTypes} = Sequelize; const Product = db.define('product',{ name: DataTypes.STRING, image: DataTypes.STRING, url: DataTypes.STRING },{ freezeTableName: true }); export default Product; (async()=>{ await db.sync(); })();
#6. Controllers
Buka file controller “ProductController.js” yang terdapat pada folder “controllers”, kemudian ketikan kode berikut:
import Product from "../models/ProductModel.js"; import path from "path"; import fs from "fs"; export const getProducts = async(req, res)=>{ try { const response = await Product.findAll(); res.json(response); } catch (error) { console.log(error.message); } } export const getProductById = async(req, res)=>{ try { const response = await Product.findOne({ where:{ id : req.params.id } }); res.json(response); } catch (error) { console.log(error.message); } } export const saveProduct = (req, res)=>{ if(req.files === null) return res.status(400).json({msg: "No File Uploaded"}); const name = req.body.title; const file = req.files.file; const fileSize = file.data.length; const ext = path.extname(file.name); const fileName = file.md5 + ext; const url = `${req.protocol}://${req.get("host")}/images/${fileName}`; const allowedType = ['.png','.jpg','.jpeg']; if(!allowedType.includes(ext.toLowerCase())) return res.status(422).json({msg: "Invalid Images"}); if(fileSize > 5000000) return res.status(422).json({msg: "Image must be less than 5 MB"}); file.mv(`./public/images/${fileName}`, async(err)=>{ if(err) return res.status(500).json({msg: err.message}); try { await Product.create({name: name, image: fileName, url: url}); res.status(201).json({msg: "Product Created Successfuly"}); } catch (error) { console.log(error.message); } }) } export const updateProduct = async(req, res)=>{ const product = await Product.findOne({ where:{ id : req.params.id } }); if(!product) return res.status(404).json({msg: "No Data Found"}); let fileName = ""; if(req.files === null){ fileName = product.image; }else{ const file = req.files.file; const fileSize = file.data.length; const ext = path.extname(file.name); fileName = file.md5 + ext; const allowedType = ['.png','.jpg','.jpeg']; if(!allowedType.includes(ext.toLowerCase())) return res.status(422).json({msg: "Invalid Images"}); if(fileSize > 5000000) return res.status(422).json({msg: "Image must be less than 5 MB"}); const filepath = `./public/images/${product.image}`; fs.unlinkSync(filepath); file.mv(`./public/images/${fileName}`, (err)=>{ if(err) return res.status(500).json({msg: err.message}); }); } const name = req.body.title; const url = `${req.protocol}://${req.get("host")}/images/${fileName}`; try { await Product.update({name: name, image: fileName, url: url},{ where:{ id: req.params.id } }); res.status(200).json({msg: "Product Updated Successfuly"}); } catch (error) { console.log(error.message); } } export const deleteProduct = async(req, res)=>{ const product = await Product.findOne({ where:{ id : req.params.id } }); if(!product) return res.status(404).json({msg: "No Data Found"}); try { const filepath = `./public/images/${product.image}`; fs.unlinkSync(filepath); await Product.destroy({ where:{ id : req.params.id } }); res.status(200).json({msg: "Product Deleted Successfuly"}); } catch (error) { console.log(error.message); } }
#7. Routes
Buka file “ProductRoute.js” yang terdapat pada folder “routes”, kemudian ketikan kode berikut:
import express from "express"; import { getProducts, getProductById, saveProduct, updateProduct, deleteProduct } from "../controllers/ProductController.js"; const router = express.Router(); router.get('/products', getProducts); router.get('/products/:id', getProductById); router.post('/products', saveProduct); router.patch('/products/:id', updateProduct); router.delete('/products/:id', deleteProduct); export default router;
#8. Entry Point
Buka file “index.js” yang terdapat pada folder “backend”, kemudian ketikan kode berikut:
import express from "express"; import FileUpload from "express-fileupload"; import cors from "cors"; import ProductRoute from "./routes/ProductRoute.js"; const app = express(); app.use(cors()); app.use(express.json()); app.use(FileUpload()); app.use(express.static("public")); app.use(ProductRoute); app.listen(5000, ()=> console.log('Server Up and Running...'));
Untuk memastikan aplikasi berjalan dengan baik, jalankan aplikasi dengan mengetikan perintah berikut pada terminal:
nodemon index
Jika berjalan dengan baik, maka akan terlihat seperti gambar berikut:
Sampai disini Anda telah berhasil membuat “backend”.
Untuk memastikan backend berjalan dengan baik, Anda dapat menggunakan POSTMAN atau extensions REST Client pada VS Code untuk melakukan pengujian.
#9. Front-End
Untuk front-end, saya akan menggunakan React JS.
Untuk membuat project react js, dapat dilakukan dengan banyak cara. Akan tetapi cara yang paling mudah adalah “create react app”.
Buka new terminal dan buat project react baru dengan mengetikan perintah berikut pada terminal:
npx create-react-app frontend
Dan pastikan Anda berada di folder “fullstack”, seperti gambar berikut:
Jika instalasi selesai, maka akan terdapat folder “frontend” di dalam folder “fullstack”.
Sehingga di dalam folder “fullstack” terdapat dua folder yaitu: “backend” dan “frontend” seperti gambar berikut:
Folder “backend” merupakan folder aplikasi yang dibangun sebelumnya menggunakan node.js express, sedangkan “frontend” merupakan folder aplikasi yang dibuat menggunakan React JS.
Selanjutnya, masuk kedalam folder “frontend” dengan mengetikan perintah berikut pada terminal:
cd frontend
Setelah itu, install react-router-dom, axios, dan bulma dengan mengetikan perintah berikut pada terminal:
npm install react-router-dom axios bulma
Setelah instalasi selesai, dan untuk memastikan semuanya berjalan dengan baik, ketikan perintah berikut untuk menjalankan project:
npm start
Jika berjalan dengan baik, maka akan tampil seperti gambar berikut:
#10. Components
Buat sebuah folder bernama “components” di dalam folder “frontend/src”.
Kemudian, buat file components “ProductList.js”, “AddProduct.js”, dan “EditProduct.js” pada folder “frontend/src/components”.
Seperti gambar berikut:
Kemudian buka file “ProductList.js”, kemudian ketikan kode berikut:
import React, { useState, useEffect } from "react"; import axios from "axios"; import { Link } from "react-router-dom"; const ProductList = () => { const [products, setProducts] = useState([]); useEffect(() => { getProducts(); }, []); const getProducts = async () => { const response = await axios.get("http://localhost:5000/products"); setProducts(response.data); }; const deleteProduct = async (productId) => { try { await axios.delete(`http://localhost:5000/products/${productId}`); getProducts(); } catch (error) { console.log(error); } }; return ( <div className="container mt-5"> <Link to="/add" className="button is-success"> Add New </Link> <div className="columns is-multiline mt-2"> {products.map((product) => ( <div className="column is-one-quarter" key={product.id}> <div className="card"> <div className="card-image"> <figure className="image is-4by3"> <img src={product.url} alt="Image" /> </figure> </div> <div className="card-content"> <div className="media"> <div className="media-content"> <p className="title is-4">{product.name}</p> </div> </div> </div> <footer className="card-footer"> <Link to={`edit/${product.id}`} className="card-footer-item"> Edit </Link> <a onClick={() => deleteProduct(product.id)} className="card-footer-item" > Delete </a> </footer> </div> </div> ))} </div> </div> ); }; export default ProductList;
Selanjutnya, buka file “AddProduct.js”, kemudian ketikan kode berikut:
import React, { useState } from "react"; import axios from "axios"; import { useNavigate } from "react-router-dom"; const AddProduct = () => { const [title, setTitle] = useState(""); const [file, setFile] = useState(""); const [preview, setPreview] = useState(""); const navigate = useNavigate(); const loadImage = (e) => { const image = e.target.files[0]; setFile(image); setPreview(URL.createObjectURL(image)); }; const saveProduct = async (e) => { e.preventDefault(); const formData = new FormData(); formData.append("file", file); formData.append("title", title); try { await axios.post("http://localhost:5000/products", formData, { headers: { "Content-type": "multipart/form-data", }, }); navigate("/"); } catch (error) { console.log(error); } }; return ( <div className="columns is-centered mt-5"> <div className="column is-half"> <form onSubmit={saveProduct}> <div className="field"> <label className="label">Product Name</label> <div className="control"> <input type="text" className="input" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Product Name" /> </div> </div> <div className="field"> <label className="label">Image</label> <div className="control"> <div className="file"> <label className="file-label"> <input type="file" className="file-input" onChange={loadImage} /> <span className="file-cta"> <span className="file-label">Choose a file...</span> </span> </label> </div> </div> </div> {preview ? ( <figure className="image is-128x128"> <img src={preview} alt="Preview Image" /> </figure> ) : ( "" )} <div className="field"> <div className="control"> <button type="submit" className="button is-success"> Save </button> </div> </div> </form> </div> </div> ); }; export default AddProduct;
Selanjutnya, buka file “EditProduct.js”, kemudian ketikan kode berikut:
import React, { useState, useEffect } from "react"; import axios from "axios"; import { useParams, useNavigate } from "react-router-dom"; const EditProduct = () => { const [title, setTitle] = useState(""); const [file, setFile] = useState(""); const [preview, setPreview] = useState(""); const { id } = useParams(); const navigate = useNavigate(); useEffect(() => { getProductById(); }, []); const getProductById = async () => { const response = await axios.get(`http://localhost:5000/products/${id}`); setTitle(response.data.name); setFile(response.data.image); setPreview(response.data.url); }; const loadImage = (e) => { const image = e.target.files[0]; setFile(image); setPreview(URL.createObjectURL(image)); }; const updateProduct = async (e) => { e.preventDefault(); const formData = new FormData(); formData.append("file", file); formData.append("title", title); try { await axios.patch(`http://localhost:5000/products/${id}`, formData, { headers: { "Content-type": "multipart/form-data", }, }); navigate("/"); } catch (error) { console.log(error); } }; return ( <div className="columns is-centered mt-5"> <div className="column is-half"> <form onSubmit={updateProduct}> <div className="field"> <label className="label">Product Name</label> <div className="control"> <input type="text" className="input" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Product Name" /> </div> </div> <div className="field"> <label className="label">Image</label> <div className="control"> <div className="file"> <label className="file-label"> <input type="file" className="file-input" onChange={loadImage} /> <span className="file-cta"> <span className="file-label">Choose a file...</span> </span> </label> </div> </div> </div> {preview ? ( <figure className="image is-128x128"> <img src={preview} alt="Preview Image" /> </figure> ) : ( "" )} <div className="field"> <div className="control"> <button type="submit" className="button is-success"> Update </button> </div> </div> </form> </div> </div> ); }; export default EditProduct;
#11. App.js
Buka file “App.js” pada folder “frontend/src”, kemudian ubah menjadi seperti berikut:
import {BrowserRouter, Routes, Route} from "react-router-dom"; import ProductList from "./components/ProductList"; import AddProduct from "./components/AddProduct"; import EditProduct from "./components/EditProduct"; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<ProductList/>}/> <Route path="add" element={<AddProduct/>}/> <Route path="edit/:id" element={<EditProduct/>}/> </Routes> </BrowserRouter> ); } export default App;
#12. index.js
Buka file “index.js” pada folder “frontend/src”, kemudian ubah menjadi seperti berikut:
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import "bulma/css/bulma.css"; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> );
#13. Testing
#READ
Kembali ke browser dan kunjungi URL berikut:
http://localhost:3000
Jika berjalan dengan baik, maka akan terlihat seperti gambar berikut:
Pada gambar diatas terlihat bahwa saya telah meng-insert 3 data sebelumnya.
#CREATE
Klik tombol “Add New”, maka akan tampil form seperti berikut:
Masukkan “Product Name” dan “Choose a file” maka Anda akan dapat melihat preview image yang akan di upload seperti gambar diatas, kemudian klik tombol “SAVE”.
Jika insert berhasil, maka akan terlihat penambahan data pada list product seperti gambar berikut:
#UPDATE
Untuk mengupdate data klik salah satu dari tombol “Edit”, maka akan tampil form seperti berikut:
Ubah “Product Name” atau “Choose a file”, kemudian klik tombol “UPDATE”.
Selain itu, Anda juga dapat mengupdate “Product Name” saja tanpa image.
Jika update berhasil, maka akan terlihat perubahan data pada list product seperti gambar berikut:
#DELETE
Untuk menghapus data klik salah satu dari tombol “Delete”.
Jika delete berhasil, maka data akan hilang dari list product.
Kesimpulan:
Pembahasan kali ini adalah bagaimana membuat upload, update, dan delete image menggunakan node js, express, mysql, dan react js.
Dengan demikian, Anda telah memiliki gambaran bagaimana membuat aplikasi CRUD + Image lengkap dengan backend dan frontend.
Jadi tunggu apalagi, Let’s coding!
Komentar (1)
Agus R M, 22 June 2022 17:06 - Reply
Mas Fikri salam kenal, saya tertarik dengan artikel ini sangat membantu untuk belajar programming khususnya javascript, saya sudah buat sesuai dengan panduannya, saat dijalankan setelah input product name dan upload image, lalu klik save, tapi tidak muncul menu apapun?