-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added: backend_nodejs API server (REST, WS) foundation
- Loading branch information
Showing
20 changed files
with
684 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"type": "node", | ||
"request": "launch", | ||
"name": "Launch Program", | ||
"program": "${workspaceFolder}/bin/www.js", | ||
"console": "integratedTerminal", | ||
"env": { | ||
"NODE_ENV": "staging" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
const express = require('express') | ||
const expressWS = require('express-ws') | ||
const morgan = require('morgan') | ||
const logger = require('../logger') | ||
const bodyParser = require('body-parser') | ||
const serveStatic = require('serve-static') | ||
const users = require('./users') | ||
const ws = require('./ws') | ||
|
||
const app = express() | ||
app.websocket = expressWS(app) | ||
|
||
app.use(morgan('combined', { stream: logger.stream })) | ||
|
||
app.use(bodyParser.json()) | ||
app.use(bodyParser.urlencoded({ extended: true })) | ||
|
||
app.use(function (req, res, next) { | ||
res.header('Access-Control-Allow-Origin', '*') | ||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE') | ||
res.header('Access-Control-Allow-Headers', 'content-type') | ||
next() | ||
}) | ||
|
||
app.use('/api/users', users) | ||
|
||
app.use('/', serveStatic(`${__dirname}/public`)) | ||
app.ws('/ws', ws) | ||
|
||
module.exports = app |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
(function () { | ||
const HOST = location.origin.replace(/^http/, 'ws') | ||
const ws = new WebSocket(HOST + '/ws') | ||
|
||
const form = document.querySelector('.form') | ||
|
||
form.onsubmit = function () { | ||
const input = document.querySelector('.input') | ||
const text = input.value | ||
ws.send(text) | ||
input.value = '' | ||
input.focus() | ||
return false | ||
} | ||
|
||
ws.onmessage = function (msg) { | ||
const response = msg.data | ||
const messageList = document.querySelector('.messages') | ||
const li = document.createElement('li') | ||
li.textContent = response | ||
messageList.appendChild(li) | ||
} | ||
}()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>API server serve chat on WebSocket</title> | ||
</head> | ||
<body> | ||
<h1>API server serve chat on WebSocket</h1> | ||
<form class="form"> | ||
<input class="input" autofocus> | ||
<input class="submit" type="submit"> | ||
</form> | ||
<ul class="messages"></ul> | ||
<script src="client.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const express = require('express') | ||
const ctrl = require('./users.ctrl') | ||
|
||
const router = express.Router() | ||
|
||
router.post('/', ctrl.creates) | ||
router.get('/', ctrl.readsAll) | ||
router.get('/:id', ctrl.reads) | ||
router.put('/:id', ctrl.updates) | ||
router.delete('/:id', ctrl.deletes) | ||
|
||
module.exports = router |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
const logger = require('../../logger') | ||
const config = require('config') | ||
const db = require('../../db/db') | ||
|
||
// CRUD: to users | ||
|
||
class ErrorCode extends Error { | ||
constructor (code = 'GENERIC', status = 500, ...params) { | ||
super(...params) | ||
|
||
if (Error.captureStackTrace) { | ||
Error.captureStackTrace(this, ErrorCode) | ||
} | ||
|
||
this.code = code | ||
this.status = status | ||
} | ||
} | ||
|
||
const creates = function (req, res) { | ||
db.transaction(async function (sql) { | ||
const name = req.body.name | ||
if (!name) { | ||
throw new ErrorCode('NO_NAME', 400, `failed to parse: name=${name}`) | ||
} | ||
|
||
const rows = await sql.select('*').from('users').where('name', name) | ||
logger.debug(rows.length) // 1 | 0 | ||
if (rows.length) { | ||
throw new ErrorCode('ALREADY_EXISTS', 409, `already exists: rows.length=${rows.length}`) | ||
} | ||
|
||
const total = await sql.insert({ name: name }).into('users') | ||
logger.debug(total) // [4] | ||
if (!total) { | ||
throw new ErrorCode('NO_INSERT', 404, `failed to insert: total=${total}`) | ||
} | ||
|
||
res.status(201).json({ total: total, name: name }) | ||
}).catch(function (exc) { | ||
logger.warn(exc.message) | ||
let status = 404 | ||
if (exc instanceof ErrorCode) status = exc.status | ||
res.status(status).end() | ||
}) | ||
} | ||
|
||
const reads = function (req, res) { | ||
db.transaction(async function (sql) { | ||
const id = parseInt(req.params.id, 10) | ||
if (Number.isNaN(id)) { | ||
throw new ErrorCode('NO_NUMBER', 400, `failed to parse: id=${id}`) | ||
} | ||
|
||
const rows = await sql.select('*').from('users').where('id', id) | ||
const exists = rows.length | ||
if (!exists) { | ||
throw new ErrorCode('NO_EXIST', 404, `failed to select: id=${id}`) | ||
} | ||
|
||
const user = rows[0] | ||
logger.debug(user) // { id: 1, name: 'Alice' } | ||
return res.json(user) | ||
}).catch(function (exc) { | ||
logger.warn(exc.message) | ||
let status = 404 | ||
if (exc instanceof ErrorCode) status = exc.status | ||
return res.status(status).end() | ||
}) | ||
} | ||
|
||
const readsAll = function (req, res) { | ||
db.transaction(async function (sql) { | ||
req.query.limit = req.query.limit || config.get('API.users.limit') | ||
|
||
const limit = parseInt(req.query.limit, 10) | ||
if (Number.isNaN(limit)) { | ||
throw new ErrorCode('NO_NUMBER', 400, `failed to parse: limit=${limit}`) | ||
} | ||
|
||
const rows = await sql.select('*').from('users').limit(limit) | ||
logger.debug(rows) // [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bek' } ] | ||
return res.json(rows) | ||
}).catch(function (exc) { | ||
logger.warn(exc.message) | ||
let status = 404 | ||
if (exc instanceof ErrorCode) status = exc.status | ||
return res.status(status).end() | ||
}) | ||
} | ||
|
||
const updates = function (req, res) { | ||
db.transaction(async function (sql) { | ||
const id = parseInt(req.params.id, 10) | ||
if (Number.isNaN(id)) { | ||
throw new ErrorCode('NO_NUMBER', 404, `failed to parse: id=${id}`) | ||
} | ||
|
||
const name = req.body.name | ||
if (!name) { | ||
throw new ErrorCode('NO_NAME', 400, `failed to parse: name=${name}`) | ||
} | ||
|
||
const updated = await sql.update('name', name).from('users').where('id', id) | ||
logger.debug(updated) // 1 | 0 | ||
if (!updated) { | ||
throw new ErrorCode('NO_UPDATE', 404, `failed to update: id=${id}`) | ||
} | ||
|
||
return res.status(204).end() | ||
}).catch(function (exc) { | ||
logger.warn(exc.message) | ||
let status = 404 | ||
if (exc instanceof ErrorCode) status = exc.status | ||
return res.status(status).end() | ||
}) | ||
} | ||
|
||
const deletes = function (req, res) { | ||
db.transaction(async function (sql) { | ||
const id = parseInt(req.params.id, 10) | ||
if (Number.isNaN(id)) { | ||
throw new ErrorCode('NO_NUMBER', 400, `failed to parse: id=${id}`) | ||
} | ||
|
||
const deleted = await sql.del().from('users').where('id', id) | ||
logger.debug(deleted) // 1 | 0 | ||
if (!deleted) { | ||
throw new ErrorCode('NO_DELETE', 404, `failed to delete: id=${id}`) | ||
} | ||
|
||
return res.status(204).end() | ||
}).catch(function (exc) { | ||
logger.warn(exc.message) | ||
let status = 404 | ||
if (exc instanceof ErrorCode) status = exc.status | ||
return res.status(status).end() | ||
}) | ||
} | ||
|
||
module.exports = { | ||
creates, | ||
reads, | ||
readsAll, | ||
updates, | ||
deletes | ||
} |
Oops, something went wrong.