├── home.png ├── .env ├── upload └── images │ ├── images_1629828987916.png │ ├── images_1629828987918.png │ ├── images_1629828987920.png │ ├── image_1_1629828165660.png │ ├── image_2_1629828165661.png │ └── profile_picture_1629827409391.png ├── utils └── appError.js ├── .gitignore ├── config └── database.js ├── package.json ├── api ├── gallery │ ├── gallery.router.js │ ├── gallery.controller.js │ └── gallery.service.js ├── vehicle │ ├── vehicle.router.js │ ├── vehicle.controller.js │ └── vehicle.service.js └── profile │ ├── profile.controller.js │ ├── profile.router.js │ └── profile.service.js ├── app.js ├── LICENSE ├── my_db.sql ├── README.md └── image_upload_example.postman_collection.json /home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/home.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | DB_PORT=3306 3 | DB_HOST=localhost 4 | DB_USER=root 5 | DB_PASS= 6 | MYSQL_DB=my_db 7 | JWT_KEY=pwt123 8 | -------------------------------------------------------------------------------- /upload/images/images_1629828987916.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/upload/images/images_1629828987916.png -------------------------------------------------------------------------------- /upload/images/images_1629828987918.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/upload/images/images_1629828987918.png -------------------------------------------------------------------------------- /upload/images/images_1629828987920.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/upload/images/images_1629828987920.png -------------------------------------------------------------------------------- /upload/images/image_1_1629828165660.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/upload/images/image_1_1629828165660.png -------------------------------------------------------------------------------- /upload/images/image_2_1629828165661.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/upload/images/image_2_1629828165661.png -------------------------------------------------------------------------------- /upload/images/profile_picture_1629827409391.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sameera-Perera/Express-Js-REST-API-Image-Uploade-Complete-Example/HEAD/upload/images/profile_picture_1629827409391.png -------------------------------------------------------------------------------- /utils/appError.js: -------------------------------------------------------------------------------- 1 | class AppError extends Error { 2 | constructor(message, statusCode) { 3 | super(message); 4 | this.statusCode = statusCode; 5 | this.isOperational = true; 6 | 7 | Error.captureStackTrace(this, this.constructor); 8 | } 9 | } 10 | 11 | module.exports = AppError; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store* 3 | Icon? 4 | ._* 5 | 6 | # Windows 7 | Thumbs.db 8 | ehthumbs.db 9 | Desktop.ini 10 | 11 | # Linux 12 | .directory 13 | *~ 14 | 15 | 16 | # npm 17 | node_modules 18 | package-lock.json 19 | *.log 20 | *.gz 21 | 22 | 23 | # Coveralls 24 | coverage 25 | 26 | # Benchmarking 27 | benchmarks/graphs -------------------------------------------------------------------------------- /config/database.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql2/promise'); 2 | 3 | const config = { 4 | port: process.env.DB_PORT, 5 | host: process.env.DB_HOST, 6 | user: process.env.DB_USER, 7 | password: process.env.DB_PASS, 8 | database: process.env.MYSQL_DB, 9 | connectionLimit: 50 10 | } 11 | 12 | const pool = mysql.createPool(config); 13 | 14 | module.exports = pool; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon app.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "dotenv": "^10.0.0", 14 | "express": "^4.17.1", 15 | "multer": "^1.4.2", 16 | "mysql2": "^2.2.5" 17 | }, 18 | "devDependencies": { 19 | "nodemon": "^2.0.12" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/gallery/gallery.router.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const { 3 | find, 4 | create 5 | } = require("./gallery.controller"); 6 | const multer = require("multer"); 7 | const path = require("path"); 8 | 9 | const storage = multer.diskStorage({ 10 | destination: './upload/images', 11 | filename: (req, file, cb) => { 12 | return cb(null, `${file.fieldname}_${Date.now()}${path.extname(file.originalname)}`) 13 | } 14 | }); 15 | const upload = multer({ 16 | storage: storage, 17 | }).array('images'); 18 | 19 | router.get("/", find); 20 | router.post("/", upload, create); 21 | 22 | 23 | module.exports = router; -------------------------------------------------------------------------------- /api/vehicle/vehicle.router.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const { 3 | find, 4 | create 5 | } = require("./vehicle.controller"); 6 | const multer = require("multer"); 7 | const path = require("path"); 8 | 9 | const storage = multer.diskStorage({ 10 | destination: './upload/images', 11 | filename: (req, file, cb) => { 12 | return cb(null, `${file.fieldname}_${Date.now()}${path.extname(file.originalname)}`) 13 | } 14 | }); 15 | const upload = multer({ 16 | storage: storage, 17 | }).fields([{ 18 | name: 'image_1', 19 | maxCount: 1 20 | }, { 21 | name: 'image_2', 22 | maxCount: 1 23 | }]); 24 | 25 | 26 | router.get("/", find); 27 | router.post("/", upload, create); 28 | 29 | 30 | module.exports = router; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const express = require("express"); 3 | const app = express(); 4 | const AppError = require("./utils/appError"); 5 | const profiles = require("./api/profile/profile.router"); 6 | const vehicles = require("./api/vehicle/vehicle.router"); 7 | const gallery = require("./api/gallery/gallery.router"); 8 | 9 | app.use(express.json()); 10 | app.use('/upload', express.static('upload/images')); 11 | app.use("/api/profiles", profiles); 12 | app.use("/api/vehicles", vehicles); 13 | app.use("/api/galleries", gallery); 14 | 15 | app.all('*', (req, res, next) => { 16 | throw new AppError(`Requested URL ${req.path} not found!`, 404); 17 | }); 18 | 19 | const port = process.env.PORT || 4000; 20 | app.listen(port, () => { 21 | console.log("server up and running on PORT :", port); 22 | }); -------------------------------------------------------------------------------- /api/gallery/gallery.controller.js: -------------------------------------------------------------------------------- 1 | const { 2 | find, 3 | create 4 | } = require("./gallery.service"); 5 | 6 | module.exports = { 7 | find: async(req, res) => { 8 | try { 9 | const result = await find(); 10 | return res.status(200).json(result); 11 | } catch (e) { 12 | return res.status(500).json({ 13 | "message": "Internal Server Error" 14 | }); 15 | } 16 | }, 17 | create: async(req, res) => { 18 | try { 19 | var gallery = req.body; 20 | var gallery_image = req.files; 21 | 22 | const result = await create(gallery, gallery_image); 23 | return res.status(201).json(result); 24 | } catch (e) { 25 | return res.status(500).json({ 26 | "message": "Internal Server Error" 27 | }); 28 | } 29 | }, 30 | }; -------------------------------------------------------------------------------- /api/profile/profile.controller.js: -------------------------------------------------------------------------------- 1 | const { 2 | find, 3 | create 4 | } = require("./profile.service"); 5 | 6 | module.exports = { 7 | find: async(req, res) => { 8 | try { 9 | const result = await find(); 10 | return res.status(200).json(result); 11 | } catch (e) { 12 | return res.status(500).json({ 13 | "message": "Internal Server Error" 14 | }); 15 | } 16 | }, 17 | create: async(req, res) => { 18 | try { 19 | var data = req.body; 20 | if (req.file) 21 | data["profile_picture"] = req.file.filename; 22 | 23 | const result = await create(data); 24 | return res.status(201).json(result); 25 | } catch (e) { 26 | return res.status(500).json({ 27 | "message": "Internal Server Error" 28 | }); 29 | } 30 | } 31 | }; -------------------------------------------------------------------------------- /api/profile/profile.router.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const { 3 | find, 4 | create 5 | } = require("./profile.controller"); 6 | const multer = require("multer"); 7 | const path = require("path"); 8 | const AppError = require("./../../utils/appError"); 9 | 10 | const storage = multer.diskStorage({ 11 | destination: './upload/images', 12 | filename: (req, file, cb) => { 13 | return cb(null, `${file.fieldname}_${Date.now()}${path.extname(file.originalname)}`) 14 | } 15 | }); 16 | const upload = multer({ 17 | storage: storage, 18 | fileFilter: function(req, file, cb) { 19 | if (!file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG|gif|GIF)$/)) { 20 | req.fileValidationError = 'Only image files are allowed!'; 21 | return cb(new Error('Only image files are allowed!'), false); 22 | } 23 | cb(null, true); 24 | }, 25 | }).single("profile_picture"); 26 | 27 | router.get("/", find); 28 | router.post("/", upload, create); 29 | 30 | 31 | module.exports = router; -------------------------------------------------------------------------------- /api/vehicle/vehicle.controller.js: -------------------------------------------------------------------------------- 1 | const { 2 | find, 3 | create 4 | } = require("./vehicle.service"); 5 | 6 | module.exports = { 7 | find: async(req, res) => { 8 | try { 9 | const result = await find(); 10 | return res.status(200).json(result); 11 | } catch (e) { 12 | return res.status(500).json({ 13 | "message": "Internal Server Error" 14 | }); 15 | } 16 | }, 17 | create: async(req, res) => { 18 | try { 19 | var data = req.body; 20 | if (req.files["image_1"]) 21 | data["image_1"] = req.files["image_1"][0].filename; 22 | 23 | if (req.files["image_2"]) 24 | data["image_2"] = req.files["image_2"][0].filename; 25 | 26 | const result = await create(data); 27 | return res.status(201).json(result); 28 | } catch (e) { 29 | return res.status(500).json({ 30 | "message": "Internal Server Error" 31 | }); 32 | } 33 | }, 34 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sameera Perera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /api/vehicle/vehicle.service.js: -------------------------------------------------------------------------------- 1 | const pool = require("../../config/database"); 2 | 3 | module.exports = { 4 | find: async() => { 5 | const connection = await pool.getConnection(); 6 | try { 7 | await connection.beginTransaction(); 8 | const fetchResult = await connection.query( 9 | `SELECT id,name,year,CONCAT('http://192.168.8.140:3000/upload/',image_2) as image_1, 10 | CONCAT('http://192.168.8.140:3000/upload/',image_1) as image_2 11 | FROM vehicle`, 12 | ); 13 | await connection.commit(); 14 | return fetchResult[0]; 15 | } catch (error) { 16 | return error; 17 | } finally { 18 | connection.release(); 19 | } 20 | }, 21 | create: async(data) => { 22 | const connection = await pool.getConnection(); 23 | try { 24 | await connection.beginTransaction(); 25 | const queryResult = await connection.query( 26 | `insert into vehicle 27 | (name,year,image_1,image_2) 28 | values(?,?,?,?)`, [ 29 | data.name, 30 | data.year, 31 | data.image_1, 32 | data.image_2 33 | ] 34 | ); 35 | const fetchResult = await connection.query( 36 | `SELECT id,name,year,CONCAT('http://192.168.8.140:3000/upload/',image_1) as image_1, 37 | CONCAT('http://192.168.8.140:3000/upload/',image_2) as image_2 38 | FROM vehicle WHERE id = ?`, [queryResult[0].insertId] 39 | ); 40 | await connection.commit(); 41 | return fetchResult[0][0]; 42 | } catch (error) { 43 | return error; 44 | } finally { 45 | connection.release(); 46 | } 47 | }, 48 | }; -------------------------------------------------------------------------------- /api/profile/profile.service.js: -------------------------------------------------------------------------------- 1 | const pool = require("../../config/database"); 2 | 3 | module.exports = { 4 | find: async() => { 5 | const connection = await pool.getConnection(); 6 | try { 7 | await connection.beginTransaction(); 8 | const fetchResult = await connection.query( 9 | `SELECT id,first_name,last_name,address,contact_number, 10 | CONCAT('http://192.168.8.140:3000/upload/',profile_picture) AS profile_picture 11 | FROM profile`, 12 | ); 13 | await connection.commit(); 14 | return fetchResult[0]; 15 | } catch (error) { 16 | return error; 17 | } finally { 18 | connection.release(); 19 | } 20 | }, 21 | create: async(data) => { 22 | const connection = await pool.getConnection(); 23 | try { 24 | await connection.beginTransaction(); 25 | const queryResult = await connection.query( 26 | `insert into profile 27 | (first_name,last_name,address,contact_number,profile_picture) 28 | values(?,?,?,?,?)`, [ 29 | data.first_name, 30 | data.last_name, 31 | data.address, 32 | data.contact_number, 33 | data.profile_picture 34 | ] 35 | ); 36 | const fetchResult = await connection.query( 37 | `SELECT id,first_name,last_name,address,contact_number, 38 | CONCAT('http://192.168.8.140:3000/upload/',profile_picture) AS profile_picture 39 | FROM profile WHERE id = ?`, [queryResult[0].insertId] 40 | ); 41 | await connection.commit(); 42 | return fetchResult[0][0]; 43 | } catch (error) { 44 | return error; 45 | } finally { 46 | connection.release(); 47 | } 48 | }, 49 | }; -------------------------------------------------------------------------------- /api/gallery/gallery.service.js: -------------------------------------------------------------------------------- 1 | const pool = require("../../config/database"); 2 | 3 | module.exports = { 4 | find: async() => { 5 | const connection = await pool.getConnection(); 6 | try { 7 | await connection.beginTransaction(); 8 | let result; 9 | const fetchGalleryResult = await connection.query( 10 | `SELECT id,name 11 | FROM gallery` 12 | ); 13 | result = fetchGalleryResult[0]; 14 | for (var i in result) { 15 | const fetchGalleryImageResult = await connection.query( 16 | `SELECT id,CONCAT('http://192.168.8.140:3000/upload/',image) as url 17 | FROM gallery_image WHERE gallery_id = ?`, [result[i].id] 18 | ); 19 | result[i]['images'] = fetchGalleryImageResult[0]; 20 | } 21 | console.log(result); 22 | return result; 23 | } catch (error) { 24 | return error; 25 | } finally { 26 | connection.release(); 27 | } 28 | }, 29 | create: async(gallery, gallery_image) => { 30 | const connection = await pool.getConnection(); 31 | try { 32 | await connection.beginTransaction(); 33 | const queryResult = await connection.query( 34 | `insert into gallery 35 | (name) 36 | values(?)`, [ 37 | gallery.name 38 | ] 39 | ); 40 | const gallery_id = queryResult[0].insertId; 41 | for (var i in gallery_image) { 42 | val = gallery_image[i]; 43 | await connection.query( 44 | `insert into gallery_image 45 | (image,gallery_id) 46 | values(?,?)`, [ 47 | val.filename, 48 | gallery_id 49 | ] 50 | ); 51 | } 52 | let result; 53 | const fetchResult = await connection.query( 54 | `SELECT id,name 55 | FROM gallery WHERE id = ?`, [queryResult[0].insertId] 56 | ); 57 | const images = await connection.query( 58 | `SELECT id,CONCAT('http://192.168.8.140:3000/upload/',image) as url 59 | FROM gallery_image WHERE gallery_id = ?`, [queryResult[0].insertId] 60 | ); 61 | await connection.commit(); 62 | result = fetchResult[0][0]; 63 | result['images'] = images[0]; 64 | return result; 65 | } catch (error) { 66 | return error; 67 | } finally { 68 | connection.release(); 69 | } 70 | }, 71 | }; -------------------------------------------------------------------------------- /my_db.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 5.0.4 3 | -- https://www.phpmyadmin.net/ 4 | -- 5 | -- Host: 127.0.0.1 6 | -- Generation Time: Aug 24, 2021 at 07:27 PM 7 | -- Server version: 10.4.17-MariaDB 8 | -- PHP Version: 7.4.15 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | START TRANSACTION; 12 | SET time_zone = "+00:00"; 13 | 14 | 15 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 16 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 17 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 18 | /*!40101 SET NAMES utf8mb4 */; 19 | 20 | -- 21 | -- Database: `my_db` 22 | -- 23 | 24 | -- -------------------------------------------------------- 25 | 26 | -- 27 | -- Table structure for table `gallery` 28 | -- 29 | 30 | CREATE TABLE `gallery` ( 31 | `id` int(11) NOT NULL, 32 | `name` varchar(255) NOT NULL 33 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 34 | 35 | -- -------------------------------------------------------- 36 | 37 | -- 38 | -- Table structure for table `gallery_image` 39 | -- 40 | 41 | CREATE TABLE `gallery_image` ( 42 | `id` int(11) NOT NULL, 43 | `image` varchar(255) NOT NULL, 44 | `gallery_id` int(11) NOT NULL 45 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 46 | 47 | -- -------------------------------------------------------- 48 | 49 | -- 50 | -- Table structure for table `profile` 51 | -- 52 | 53 | CREATE TABLE `profile` ( 54 | `id` int(11) NOT NULL, 55 | `first_name` varchar(255) NOT NULL, 56 | `last_name` varchar(255) NOT NULL, 57 | `address` varchar(255) NOT NULL, 58 | `contact_number` varchar(12) NOT NULL, 59 | `profile_picture` varchar(255) NOT NULL 60 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 61 | 62 | -- -------------------------------------------------------- 63 | 64 | -- 65 | -- Table structure for table `vehicle` 66 | -- 67 | 68 | CREATE TABLE `vehicle` ( 69 | `id` int(11) NOT NULL, 70 | `name` varchar(255) NOT NULL, 71 | `year` varchar(255) NOT NULL, 72 | `image_1` varchar(255) NOT NULL, 73 | `image_2` varchar(255) NOT NULL 74 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 75 | 76 | -- 77 | -- Indexes for dumped tables 78 | -- 79 | 80 | -- 81 | -- Indexes for table `gallery` 82 | -- 83 | ALTER TABLE `gallery` 84 | ADD PRIMARY KEY (`id`); 85 | 86 | -- 87 | -- Indexes for table `gallery_image` 88 | -- 89 | ALTER TABLE `gallery_image` 90 | ADD PRIMARY KEY (`id`), 91 | ADD KEY `gallery_id` (`gallery_id`); 92 | 93 | -- 94 | -- Indexes for table `profile` 95 | -- 96 | ALTER TABLE `profile` 97 | ADD PRIMARY KEY (`id`); 98 | 99 | -- 100 | -- Indexes for table `vehicle` 101 | -- 102 | ALTER TABLE `vehicle` 103 | ADD PRIMARY KEY (`id`); 104 | 105 | -- 106 | -- AUTO_INCREMENT for dumped tables 107 | -- 108 | 109 | -- 110 | -- AUTO_INCREMENT for table `gallery` 111 | -- 112 | ALTER TABLE `gallery` 113 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; 114 | 115 | -- 116 | -- AUTO_INCREMENT for table `gallery_image` 117 | -- 118 | ALTER TABLE `gallery_image` 119 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; 120 | 121 | -- 122 | -- AUTO_INCREMENT for table `profile` 123 | -- 124 | ALTER TABLE `profile` 125 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; 126 | 127 | -- 128 | -- AUTO_INCREMENT for table `vehicle` 129 | -- 130 | ALTER TABLE `vehicle` 131 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; 132 | 133 | -- 134 | -- Constraints for dumped tables 135 | -- 136 | 137 | -- 138 | -- Constraints for table `gallery_image` 139 | -- 140 | ALTER TABLE `gallery_image` 141 | ADD CONSTRAINT `gallery_image_ibfk_1` FOREIGN KEY (`gallery_id`) REFERENCES `gallery` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; 142 | COMMIT; 143 | 144 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 145 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 146 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Contributors][contributors-shield]][contributors-url] 2 | [![Forks][forks-shield]][forks-url] 3 | [![Stargazers][stars-shield]][stars-url] 4 | [![Issues][issues-shield]][issues-url] 5 | [![LinkedIn][linkedin-shield]][linkedin-url] 6 | 7 |
8 |