├── 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 |
4 |
5 |
6 | NodeJS Users API - Without Frameworks And Packages
7 |
8 |
9 |
10 | [](/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()
--------------------------------------------------------------------------------