├── LICENSE ├── README.md ├── data └── users.json └── src ├── config ├── bodyParser.js ├── paths.js └── server │ ├── serverConfig.js │ └── serverConfigFunctions.js ├── controllers └── userController.js ├── factories └── User.js ├── repositories ├── Repository.js └── userRepository.js ├── routes.js ├── server.js └── services ├── createUserService.js ├── deleteUserByIdService.js ├── findAllUsersService.js ├── getUserByIdService.js └── updateUserService.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2021 Nathan Cotrim Lemos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 8 | to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 15 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 16 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Project logo 4 |

5 | 6 |

NodeJS Users API - Without Frameworks And Packages

7 | 8 |
9 | 10 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](/LICENSE) 11 | 12 |
13 | 14 | --- 15 | 16 | ## 📝 Table of Contents 17 | 18 | - [About](#about) 19 | - [Getting Started](#getting_started) 20 | - [Usage](#usage) 21 | - [Built Using](#built_using) 22 | - [Authors](#author) 23 | 24 |
25 | 26 | ## 🧐 About 27 | 28 | Purpose of this project was build a simple users API in a different way we see all time in nodeJS ecosystem, usually using Express, Adonis, Hapi... 29 | I used just the native modules of nodeJS and a JSON file as DataBank, that is managed using node fs 30 |
31 |
32 | 33 |
34 |
35 | 36 | ## 🏁 Getting Started 37 |
38 | 39 | ### Installing 40 | 41 | ``` 42 | git clone https://github.com/NathanCotrim/NodeJS-API.git 43 | ``` 44 | 45 | or - (GitHub CLI) 46 | 47 | ``` 48 | gh repo clone NathanCotrim/NodeJS-API 49 | ``` 50 |
51 | 52 | ### Running 53 | 54 | ``` 55 | node src/server.js 56 | ``` 57 |
58 | 59 | ## 🎈 Usage 60 |
61 | 62 | ### Main API Routes 63 | 64 |
65 | 66 | #### http://localhost:3000/users 67 |

GET | List all users

68 | 69 |
70 | 71 | #### http://localhost:3000/user/:id 72 |

GET | Get user by Id

73 | 74 |
75 | 76 | #### http://localhost:3000/new/user 77 |

POST | Create User

78 | 79 |

Receives a JSON:

80 | 81 | ``` 82 | { 83 | "name": "Tester", 84 | "email": "tester.test@domain.com" 85 | } 86 | ``` 87 | 88 |
89 | 90 | #### http://localhost:3000/update/user/:id 91 |

PATCH | Update User

92 | 93 |

Receives a JSON with new Information:

94 | 95 | ``` 96 | { 97 | "name": "TesterUpdated", (?) 98 | "email": "testerUpdated.test@domain.com" (?) 99 | } 100 | ``` 101 | 102 |
103 | 104 | #### http://localhost:3000/delete/user/:id 105 |

DELETE | Delete User

106 | 107 | 108 |
109 |
110 | 111 | ### To request any route without an app use: 112 | ``` 113 | curl ${url} -X ${method} 114 | ``` 115 | 116 |
117 | 118 | ## ⛏️ Built Using 119 | 120 | - [NodeJs](https://nodejs.org/en/) - Server Environment 121 | 122 |
123 | 124 | ## ✍️ Author - Nathan Cotrim - MIT License 125 | 126 | -------------------------------------------------------------------------------- /data/users.json: -------------------------------------------------------------------------------- 1 | [{"name":"Than","email":"nathan.cotrim@gmail.com","id":1},{"name":"Gabs","email":"gabriel.cotrim@gmail.com","id":2}] -------------------------------------------------------------------------------- /src/config/bodyParser.js: -------------------------------------------------------------------------------- 1 | module.exports = (request, action) => { 2 | let body; 3 | 4 | request.on('data', (chunk) => { 5 | body = JSON.parse(chunk) 6 | }) 7 | 8 | request.on('end', () => { 9 | request.body = body 10 | action() 11 | }) 12 | } -------------------------------------------------------------------------------- /src/config/paths.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | 3 | module.exports = { 4 | usersDataPath: join(__dirname, '..', '..', 'data', 'users.json') 5 | } 6 | -------------------------------------------------------------------------------- /src/config/server/serverConfig.js: -------------------------------------------------------------------------------- 1 | const { 2 | setResponseSendMethod, 3 | implementsBodyParserUseCase, 4 | verifyRouteUseParams, 5 | getRoute, 6 | generateUrlWithParam 7 | } = require('./serverConfigFunctions') 8 | 9 | module.exports = (req, res) => { 10 | setResponseSendMethod(res) 11 | 12 | let { url, method } = req 13 | console.log(`method: ${method} | endpoint: ${url}`); 14 | 15 | if (verifyRouteUseParams(url)) { 16 | const { id, parseUrl } = generateUrlWithParam(url) 17 | req.params = { id } 18 | url = `${parseUrl}:id` 19 | } 20 | 21 | const route = getRoute(method, url) 22 | if (!route) { 23 | return res.send(404, `Cannot ${method} at ${url}`) 24 | } 25 | 26 | implementsBodyParserUseCase(method, route.action, req, res) 27 | } -------------------------------------------------------------------------------- /src/config/server/serverConfigFunctions.js: -------------------------------------------------------------------------------- 1 | const routes = require('../../routes') 2 | const bodyParser = require('../bodyParser') 3 | 4 | module.exports = { 5 | setResponseSendMethod(res) { 6 | res.send = (statusCode, body, content_type = 'application/json ') => { 7 | res.writeHead(statusCode, { 8 | 'Content-Type': content_type 9 | }) 10 | 11 | if (content_type !== 'application/json ') { 12 | return res.end(body) 13 | } 14 | 15 | return res.end(JSON.stringify(body)) 16 | } 17 | }, 18 | 19 | getRoute(method, url) { 20 | return routes.find((iterationCurrentRoute) => 21 | iterationCurrentRoute.method === method && 22 | iterationCurrentRoute.endpoint === url 23 | ) 24 | }, 25 | 26 | implementsBodyParserUseCase(method, action, req, res) { 27 | if (method !== 'GET') { 28 | return bodyParser(req, () => action(req, res)) 29 | } else { 30 | return action(req, res) 31 | } 32 | }, 33 | 34 | verifyRouteUseParams(url) { 35 | if (url.match(/\d+/)) { 36 | return true 37 | } 38 | }, 39 | 40 | generateUrlWithParam(url) { 41 | const id = /\d+/.exec(url)[0] 42 | const parseUrl = url.replace(/\d+/, '') 43 | return { id, parseUrl } 44 | } 45 | } -------------------------------------------------------------------------------- /src/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const createUserService = require("../services/createUserService") 2 | const deleteUserByIdService = require("../services/deleteUserByIdService") 3 | const listUsersService = require("../services/findAllUsersService") 4 | const getUserByIdService = require("../services/getUserByIdService") 5 | const updateUserByIdService = require("../services/updateUserService") 6 | 7 | class UserController { 8 | async listUsers(_, res) { 9 | try { 10 | return res.send(200, await listUsersService.listUsers()) 11 | } catch (error) { 12 | return res.send(400, { 13 | error: error.message 14 | }) 15 | } 16 | } 17 | 18 | async getUserById(req, res) { 19 | const { id } = req.params 20 | 21 | try { 22 | const user = await getUserByIdService.getUserById(id) 23 | 24 | return res.send(200, user) 25 | } catch (error) { 26 | return res.send(400, { 27 | error: error.message 28 | }) 29 | } 30 | } 31 | 32 | async createUser(req, res) { 33 | const { name, email } = req.body 34 | 35 | try { 36 | const newUser = await createUserService.createUser(name, email) 37 | 38 | return res.send(200, newUser) 39 | } catch (error) { 40 | return res.send(400, { 41 | error: error.message 42 | }) 43 | } 44 | 45 | } 46 | 47 | async deleteUser(req, res) { 48 | const { id } = req.params 49 | 50 | try { 51 | const user = await deleteUserByIdService.deleteUserById(id) 52 | 53 | return res.send(200, user) 54 | } catch (error) { 55 | return res.send(400, { 56 | error: error.message 57 | }) 58 | } 59 | } 60 | 61 | async updateUser(req, res) { 62 | const { body } = req 63 | const { id } = req.params 64 | 65 | try { 66 | const newUser = await updateUserByIdService.updateUserById(id, body) 67 | 68 | return res.send(200, newUser) 69 | } catch (error) { 70 | return res.send(400, { 71 | error: error.message 72 | }) 73 | } 74 | } 75 | } 76 | 77 | module.exports = new UserController() -------------------------------------------------------------------------------- /src/factories/User.js: -------------------------------------------------------------------------------- 1 | const userRepository = require('../repositories/userRepository'); 2 | 3 | class User { 4 | constructor(name, email) { 5 | (async () => { 6 | this.name = name 7 | this.email = email 8 | 9 | this.id = await this.#getNewUserId() 10 | })() 11 | } 12 | 13 | async #getNewUserId() { 14 | const users = await userRepository.findAll() 15 | 16 | if (!users[0]) { 17 | return 1 18 | } else { 19 | return users[users.length - 1].id + 1 20 | } 21 | } 22 | } 23 | 24 | module.exports = User 25 | -------------------------------------------------------------------------------- /src/repositories/Repository.js: -------------------------------------------------------------------------------- 1 | const { readFile, writeFile } = require('fs/promises') 2 | 3 | class Repository { 4 | 5 | async findAll() { 6 | const data = JSON.parse(await readFile(this.filePath)) 7 | 8 | if (!data) { return false } 9 | 10 | return data 11 | } 12 | 13 | async create(data) { 14 | const allData = await this.findAll() 15 | 16 | allData.push(data) 17 | allData.sort((data1, data2) => { 18 | return data1.id < data2.id 19 | }) 20 | 21 | await writeFile(this.filePath, JSON.stringify(allData)) 22 | 23 | return data 24 | } 25 | 26 | async findOne(searchParams) { 27 | let data = []; 28 | 29 | if (!searchParams) { 30 | const allData = await this.findAll() 31 | 32 | return allData[allData.length - 1] 33 | } 34 | 35 | const keys = Object.keys(searchParams) 36 | 37 | const allData = await this.findAll() 38 | 39 | keys.forEach(currentKey => { 40 | const currentData = allData.find((currentIterationData) => 41 | currentIterationData[currentKey] === searchParams[currentKey] 42 | ) 43 | 44 | if (currentData) { data.push(currentData) } 45 | }) 46 | 47 | if (data.length !== keys.length) { return false } 48 | 49 | return true 50 | } 51 | 52 | async findById(id) { 53 | const allData = await this.findAll() 54 | const data = allData.find((currentIterationData) => ( 55 | currentIterationData.id === id 56 | )) 57 | 58 | if (!data) { return false } 59 | 60 | data.id = parseInt(data.id) 61 | return data 62 | } 63 | 64 | async deleteById(id) { 65 | const allData = JSON.parse(await readFile(this.filePath)) 66 | const data = allData.find((currentIterationData) => ( 67 | currentIterationData.id = id 68 | )) 69 | 70 | if (!data) { return false } 71 | 72 | allData.splice([allData.indexOf(data)], 1) 73 | 74 | await writeFile(this.filePath, JSON.stringify(allData)) 75 | 76 | return data 77 | } 78 | 79 | async updateById(id, newCredentials) { 80 | const allData = await this.findAll() 81 | let data = allData.find((currentIterationData) => ( 82 | currentIterationData.id = id 83 | )) 84 | 85 | allData.splice([allData.indexOf(data)], 1) 86 | 87 | if (!data) { return false } 88 | 89 | data = { 90 | ...data, 91 | ...newCredentials 92 | } 93 | data.id = parseInt(data.id) 94 | allData.push(data) 95 | 96 | await writeFile(this.filePath, JSON.stringify(allData)) 97 | 98 | return data 99 | } 100 | 101 | } 102 | 103 | module.exports = Repository -------------------------------------------------------------------------------- /src/repositories/userRepository.js: -------------------------------------------------------------------------------- 1 | const Repository = require('./Repository') 2 | const User = require("../factories/User") 3 | 4 | class UserRepository extends Repository { 5 | constructor(filePath) { 6 | super() 7 | this.filePath = filePath 8 | } 9 | } 10 | 11 | 12 | 13 | const { usersDataPath } = require('../config/paths') 14 | module.exports = new UserRepository(usersDataPath) -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | const { listUsers, getUserById, createUser, deleteUser, updateUser } = require('./controllers/userController') 2 | 3 | module.exports = [ 4 | { 5 | method: 'GET', 6 | endpoint: '/users', 7 | action: listUsers 8 | }, 9 | { 10 | method: 'GET', 11 | endpoint: '/user/:id', 12 | action: getUserById 13 | }, 14 | { 15 | method: 'POST', 16 | endpoint: '/new/user', 17 | action: createUser 18 | }, 19 | { 20 | method: 'DELETE', 21 | endpoint: '/delete/user/:id', 22 | action: deleteUser 23 | }, 24 | { 25 | method: 'PATCH', 26 | endpoint: '/update/user/:id', 27 | action: updateUser 28 | } 29 | ] -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const serverConfig = require('./config/server/serverConfig') 3 | 4 | const server = http.createServer(async (req, res) => { 5 | serverConfig(req, res) 6 | }) 7 | 8 | server.listen(3000, () => console.log("✔ server is running at port 3000")); -------------------------------------------------------------------------------- /src/services/createUserService.js: -------------------------------------------------------------------------------- 1 | const User = require("../factories/User") 2 | const userRepository = require("../repositories/userRepository") 3 | const { readFile } = require('fs/promises') 4 | 5 | class CreateUserService { 6 | async #verifyIfCredentialsAlreadyBeingUsed(name, email) { 7 | const users = await userRepository.findAll() 8 | 9 | const user = users.find((iterationCurrentUser) => 10 | name === iterationCurrentUser.name || 11 | email === iterationCurrentUser.email 12 | ) 13 | 14 | if (user) { 15 | return true 16 | } 17 | 18 | return false 19 | } 20 | 21 | async createUser(name, email) { 22 | if (!email || !name) { 23 | throw new Error("send valid values") 24 | } 25 | 26 | const credentialsAlreadyBeingUsed = 27 | await this.#verifyIfCredentialsAlreadyBeingUsed(name, email) 28 | 29 | if (credentialsAlreadyBeingUsed) { 30 | throw new Error("email or name already being used") 31 | } 32 | 33 | const newUser = await userRepository.create( 34 | new User( 35 | name, 36 | email 37 | ) 38 | ) 39 | 40 | return { 41 | ...newUser 42 | } 43 | } 44 | 45 | } 46 | 47 | module.exports = new CreateUserService() 48 | -------------------------------------------------------------------------------- /src/services/deleteUserByIdService.js: -------------------------------------------------------------------------------- 1 | const userRepository = require("../repositories/userRepository") 2 | 3 | class GetUserByIdService { 4 | async deleteUserById(id) { 5 | const user = await userRepository.deleteById(id) 6 | 7 | if (!user) { 8 | throw new Error("User Inexistent") 9 | } 10 | 11 | return user 12 | } 13 | } 14 | 15 | module.exports = new GetUserByIdService() -------------------------------------------------------------------------------- /src/services/findAllUsersService.js: -------------------------------------------------------------------------------- 1 | const userRepository = require('../repositories/userRepository') 2 | 3 | class CreateUserService { 4 | async listUsers() { 5 | const users = await userRepository.findAll() 6 | 7 | if (!users[0]) { 8 | throw new Error("No Users Founded") 9 | } 10 | 11 | return users 12 | } 13 | } 14 | 15 | module.exports = new CreateUserService() -------------------------------------------------------------------------------- /src/services/getUserByIdService.js: -------------------------------------------------------------------------------- 1 | const userRepository = require("../repositories/userRepository") 2 | 3 | class GetUserByIdService { 4 | async getUserById(id) { 5 | const user = await userRepository.findById(id) 6 | 7 | if (!user) { 8 | throw new Error("User Inexistent") 9 | } 10 | 11 | return user 12 | } 13 | } 14 | 15 | module.exports = new GetUserByIdService() -------------------------------------------------------------------------------- /src/services/updateUserService.js: -------------------------------------------------------------------------------- 1 | const userRepository = require("../repositories/userRepository") 2 | 3 | class UpdateUserService { 4 | #validateNewCredentials(newCredentialsKeys) { 5 | let keysAreCorrect = false; 6 | 7 | newCredentialsKeys.forEach(key => { 8 | if (key === 'email' || key === 'name') { 9 | keysAreCorrect = true 10 | } 11 | }); 12 | 13 | if (!keysAreCorrect) { 14 | console.log('Incorrect keys'); 15 | return false 16 | } 17 | 18 | if (newCredentialsKeys.length > 2) { 19 | console.log('length maior que 2'); 20 | return false 21 | } 22 | 23 | return keysAreCorrect 24 | } 25 | 26 | async #verifyIfNewCredentialsAlreadyBeingUsed(newCredentials) { 27 | const user = await userRepository.findOne(newCredentials) 28 | 29 | if (user) { 30 | return true 31 | } 32 | } 33 | 34 | async #executeValidations(newCredentials) { 35 | const keys = Object.keys(newCredentials) 36 | 37 | if (keys.length === 0) { 38 | return { 39 | isValid: false, 40 | errorMessage: "Values cannot be null" 41 | } 42 | } 43 | 44 | if (await this.#verifyIfNewCredentialsAlreadyBeingUsed(newCredentials)) { 45 | return { 46 | isValid: false, 47 | errorMessage: "New values already being used" 48 | } 49 | } 50 | 51 | if (!this.#validateNewCredentials(keys)) { 52 | return { 53 | isValid: false, 54 | errorMessage: "Invalid Values" 55 | } 56 | } 57 | 58 | return { 59 | isValid: true 60 | } 61 | } 62 | 63 | async updateUserById(id, newCredentials) { 64 | const validation = await this.#executeValidations(newCredentials) 65 | 66 | if (!validation.isValid) { 67 | throw new Error(validation.errorMessage) 68 | } 69 | 70 | const user = await userRepository.updateById(id, newCredentials) 71 | 72 | if (!user) { throw new Error("User not Found") } 73 | 74 | return user 75 | } 76 | } 77 | 78 | 79 | module.exports = new UpdateUserService() --------------------------------------------------------------------------------