Chapter 11: initial samples
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
var createError = require("http-errors");
|
||||
var express = require("express");
|
||||
var path = require("path");
|
||||
var cookieParser = require("cookie-parser");
|
||||
var logger = require("morgan");
|
||||
|
||||
var indexRouter = require("./routes/index");
|
||||
var usersRouter = require("./routes/users");
|
||||
var inventoryRouter = require("./routes/inventory");
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.use(logger("dev"));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
|
||||
app.use("/", indexRouter);
|
||||
app.use("/users", usersRouter);
|
||||
app.use("/inventory", inventoryRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function (err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get("env") === "development" ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render("error");
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
console.log("Unhandled Rejection at:", promise, "reason:", reason);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('bookstore-web-app:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var usersRouter = require('./routes/users');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('bookstore-web-appcd:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "bookstore-web-appcd",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"name": "bookstore-web-app",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.5.2",
|
||||
"raw-body": "2.3.3",
|
||||
"type-is": "~1.6.16"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
|
||||
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
|
||||
"requires": {
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6"
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz",
|
||||
"integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.5",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.18.3",
|
||||
"content-disposition": "0.5.2",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.1.1",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.4",
|
||||
"qs": "6.5.2",
|
||||
"range-parser": "~1.2.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.16.2",
|
||||
"serve-static": "1.13.2",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": "~1.4.0",
|
||||
"type-is": "~1.6.16",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.4.0",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": ">= 1.4.0 < 2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.44.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
|
||||
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.27",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
|
||||
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
|
||||
"requires": {
|
||||
"mime-db": "1.44.0"
|
||||
}
|
||||
},
|
||||
"morgan": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
|
||||
"integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
|
||||
"requires": {
|
||||
"basic-auth": "~2.0.0",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"on-headers": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.6.2",
|
||||
"mime": "1.4.1",
|
||||
"ms": "2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.0",
|
||||
"statuses": "~1.4.0"
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.2",
|
||||
"send": "0.16.2"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "bookstore-web-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"node-fetch": "^2.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,36 @@
|
||||
const { Router } = require("express");
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", function (req, res) {
|
||||
fetch("http://localhost:3000/books")
|
||||
.then((res) => res.json())
|
||||
.then((json) =>
|
||||
res.render("inventory", {
|
||||
books: json,
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
res.render("error", {
|
||||
error: error,
|
||||
message: error.message,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post("/add", function (req, res) {
|
||||
console.log(req.body);
|
||||
|
||||
fetch("http://localhost:3000/books", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(req.body),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
.then(res.redirect("/inventory"))
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Book Inventory</title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Book Inventory</h1>
|
||||
<ul>
|
||||
<% for(let book of books) { %>
|
||||
<li><%= book.title %> - <%= book.author %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
|
||||
<h2>Add Book:</h2>
|
||||
<form action="/inventory/add" method="POST">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" />
|
||||
<label for="author">Author</label>
|
||||
<input type="text" name="author" />
|
||||
<button type="submit" value="Submit">Submit</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var usersRouter = require('./routes/users');
|
||||
var inventoryRouter = require('./routes/inventory');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
app.use('/inventory', inventoryRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
console.log("Unhandled Rejection at:", promise, "reason:", reason);
|
||||
process.exit(1);
|
||||
});
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('bookstore-web-app:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
||||
+409
@@ -0,0 +1,409 @@
|
||||
{
|
||||
"name": "bookstore-web-app",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.2"
|
||||
}
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.5.2",
|
||||
"raw-body": "2.3.3",
|
||||
"type-is": "~1.6.16"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
|
||||
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
|
||||
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
|
||||
"requires": {
|
||||
"cookie": "0.4.0",
|
||||
"cookie-signature": "1.0.6"
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz",
|
||||
"integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.5",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.18.3",
|
||||
"content-disposition": "0.5.2",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.1.1",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.4",
|
||||
"qs": "6.5.2",
|
||||
"range-parser": "~1.2.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.16.2",
|
||||
"serve-static": "1.13.2",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": "~1.4.0",
|
||||
"type-is": "~1.6.16",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.4.0",
|
||||
"unpipe": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": ">= 1.4.0 < 2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.44.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
|
||||
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.27",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
|
||||
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
|
||||
"requires": {
|
||||
"mime-db": "1.44.0"
|
||||
}
|
||||
},
|
||||
"morgan": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz",
|
||||
"integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==",
|
||||
"requires": {
|
||||
"basic-auth": "~2.0.0",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"on-headers": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
|
||||
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
"integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.6.2",
|
||||
"mime": "1.4.1",
|
||||
"ms": "2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.0",
|
||||
"statuses": "~1.4.0"
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.2",
|
||||
"send": "0.16.2"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "bookstore-web-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.1",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"node-fetch": "^2.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,37 @@
|
||||
const { Router } = require("express");
|
||||
const fetch = require("node-fetch");
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/", function (req, res) {
|
||||
fetch("http://localhost:3000/books")
|
||||
.then((res) => res.json())
|
||||
.then((json) =>
|
||||
res.render("inventory", {
|
||||
books: json,
|
||||
})
|
||||
)
|
||||
.catch((error) => {
|
||||
res.render("error", {
|
||||
error: error,
|
||||
message: error.message,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
router.post("/add", function (req, res) {
|
||||
console.log(req.body);
|
||||
|
||||
fetch("http://localhost:3000/books", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(req.body),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
.then(res.redirect("/inventory"))
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,3 @@
|
||||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1><%= title %></h1>
|
||||
<p>Welcome to <%= title %></p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Book Inventory</title>
|
||||
<link rel='stylesheet' href='/stylesheets/style.css' />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Book Inventory</h1>
|
||||
<ul>
|
||||
<% for(let book of books) { %>
|
||||
<li><%= book.title %> - <%= book.author %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<h2>Add Book:</h2>
|
||||
<form action="/inventory/add" method="POST">
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" />
|
||||
<label for="author">Author</label>
|
||||
<input type="text" name="author" />
|
||||
<button type="submit" value="Submit">Submit</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
.git
|
||||
.gitignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
@@ -0,0 +1,62 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# 0x
|
||||
profile-*
|
||||
|
||||
# mac files
|
||||
.DS_Store
|
||||
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
# webstorm
|
||||
.idea
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
*code-workspace
|
||||
|
||||
# clinic
|
||||
profile*
|
||||
*clinic*
|
||||
*flamegraph*
|
||||
|
||||
# generated code
|
||||
*/**/*.js
|
||||
dist
|
||||
@@ -0,0 +1,22 @@
|
||||
FROM node
|
||||
|
||||
WORKDIR "/app"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get dist-upgrade -y \
|
||||
&& apt-get clean \
|
||||
&& echo 'Finished installing dependencies'
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
COPY . /app
|
||||
|
||||
ENV PORT 3000
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
USER node
|
||||
|
||||
CMD ["npm", "start"]
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const AutoLoad = require('fastify-autoload')
|
||||
|
||||
module.exports = async function (fastify, opts) {
|
||||
// Place here your custom code!
|
||||
|
||||
// Do not touch the following lines
|
||||
|
||||
// This loads all plugins defined in plugins
|
||||
// those should be support plugins that are reused
|
||||
// through your application
|
||||
fastify.register(AutoLoad, {
|
||||
dir: path.join(__dirname, 'plugins'),
|
||||
options: Object.assign({}, opts)
|
||||
})
|
||||
|
||||
// This loads all plugins defined in routes
|
||||
// define your routes in one of these
|
||||
fastify.register(AutoLoad, {
|
||||
dir: path.join(__dirname, 'routes'),
|
||||
options: Object.assign({}, opts)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: fastify-app-svc
|
||||
labels:
|
||||
run: fastify
|
||||
spec:
|
||||
selector:
|
||||
app: fastify
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 3000
|
||||
targetPort: 3000
|
||||
type: NodePort
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: fastify-app
|
||||
labels:
|
||||
app: fastify
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: fastify
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: fastify
|
||||
spec:
|
||||
containers:
|
||||
- name: fastify-app
|
||||
image: fastify-microservice:latest
|
||||
imagePullPolicy: Never
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "fastify-microservice",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "tap test/**/*.test.js",
|
||||
"start": "fastify start -l info app.js",
|
||||
"dev": "fastify start -w -l info -P app.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fastify": "^3.0.0",
|
||||
"fastify-plugin": "^2.0.0",
|
||||
"fastify-autoload": "^3.0.2",
|
||||
"fastify-cli": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tap": "^14.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# Plugins Folder
|
||||
|
||||
Plugins define behavior that is common to all the routes in your
|
||||
application. Authentication, caching, templates, and all the other cross
|
||||
cutting concerns should be handled by plugins placed in this folder.
|
||||
|
||||
Files in this folder are typically defined through the
|
||||
[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module,
|
||||
making them non-encapsulated. They can define decorators and set hooks
|
||||
that will then be used in the rest of your application.
|
||||
|
||||
Check out:
|
||||
|
||||
* [The hitchhiker's guide to plugins](https://github.com/fastify/fastify/blob/master/docs/Plugins-Guide.md)
|
||||
* [Fastify decorators](https://www.fastify.io/docs/latest/Decorators/).
|
||||
* [Fastify lifecycle](https://www.fastify.io/docs/latest/Lifecycle/).
|
||||
@@ -0,0 +1,26 @@
|
||||
# Services Folder
|
||||
|
||||
Services define routes within your application. Fastify provides an
|
||||
easy path to a microservice architecture, in the future you might want
|
||||
to independently deploy some of those.
|
||||
|
||||
In this folder you should define all the services that define the routes
|
||||
of your web application.
|
||||
Each service is a [Fastify
|
||||
plugin](https://www.fastify.io/docs/latest/Plugins/), it is
|
||||
encapsulated (it can have its own independent plugins) and it is
|
||||
typically stored in a file; be careful to group your routes logically,
|
||||
e.g. all `/users` routes in a `users.js` file. We have added
|
||||
a `root.js` file for you with a '/' root added.
|
||||
|
||||
If a single file become too large, create a folder and add a `index.js` file there:
|
||||
this file must be a Fastify plugin, and it will be loaded automatically
|
||||
by the application. You can now add as many files as you want inside that folder.
|
||||
In this way you can create complex services within a single monolith,
|
||||
and eventually extract them.
|
||||
|
||||
If you need to share functionality between services, place that
|
||||
functionality into the `plugins` folder, and share it via
|
||||
[decorators](https://www.fastify.io/docs/latest/Decorators/).
|
||||
|
||||
If you have a bit confused about using `async/await` write services, you would better take a look at [Promise resolution](https://github.com/fastify/fastify/blob/master/docs/Routes.md#promise-resolution) for more details.
|
||||
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
/dist
|
||||
# Cache used by TypeScript's incremental build
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
coverage/
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: '@loopback/eslint-config',
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# Transpiled JavaScript files from Typescript
|
||||
/dist
|
||||
|
||||
# Cache used by TypeScript's incremental build
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"recursive": true,
|
||||
"require": "source-map-support/register"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
package-lock=true
|
||||
scripts-prepend-node-path=true
|
||||
@@ -0,0 +1,2 @@
|
||||
dist
|
||||
*.json
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"bracketSpacing": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 80,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"editor.rulers": [80],
|
||||
"editor.tabCompletion": "on",
|
||||
"editor.tabSize": 2,
|
||||
"editor.trimAutoWhitespace": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true,
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
|
||||
"files.exclude": {
|
||||
"**/.DS_Store": true,
|
||||
"**/.git": true,
|
||||
"**/.hg": true,
|
||||
"**/.svn": true,
|
||||
"**/CVS": true,
|
||||
"dist": true,
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": false,
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"eslint.run": "onSave",
|
||||
"eslint.nodePath": "./node_modules",
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"typescript"
|
||||
]
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Watch and Compile Project",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["--silent", "run", "build:watch"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": "$tsc-watch"
|
||||
},
|
||||
{
|
||||
"label": "Build, Test and Lint",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": ["--silent", "run", "test:dev"],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": ["$tsc", "$eslint-compact", "$eslint-stylish"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"@loopback/cli": {
|
||||
"version": "2.10.0",
|
||||
"packageManager": "npm"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# Developer's Guide
|
||||
|
||||
We use Visual Studio Code for developing LoopBack and recommend the same to our
|
||||
users.
|
||||
|
||||
## VSCode setup
|
||||
|
||||
Install the following extensions:
|
||||
|
||||
- [eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
|
||||
- [prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||
|
||||
## Development workflow
|
||||
|
||||
### Visual Studio Code
|
||||
|
||||
1. Start the build task (Cmd+Shift+B) to run TypeScript compiler in the
|
||||
background, watching and recompiling files as you change them. Compilation
|
||||
errors will be shown in the VSCode's "PROBLEMS" window.
|
||||
|
||||
2. Execute "Run Rest Task" from the Command Palette (Cmd+Shift+P) to re-run the
|
||||
test suite and lint the code for both programming and style errors. Linting
|
||||
errors will be shown in VSCode's "PROBLEMS" window. Failed tests are printed
|
||||
to terminal output only.
|
||||
|
||||
### Other editors/IDEs
|
||||
|
||||
1. Open a new terminal window/tab and start the continuous build process via
|
||||
`npm run build:watch`. It will run TypeScript compiler in watch mode,
|
||||
recompiling files as you change them. Any compilation errors will be printed
|
||||
to the terminal.
|
||||
|
||||
2. In your main terminal window/tab, run `npm run test:dev` to re-run the test
|
||||
suite and lint the code for both programming and style errors. You should run
|
||||
this command manually whenever you have new changes to test. Test failures
|
||||
and linter errors will be printed to the terminal.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Check out https://hub.docker.com/_/node to select a new base image
|
||||
FROM node:10-slim
|
||||
|
||||
# Set to a non-root built-in user `node`
|
||||
USER node
|
||||
|
||||
# Create app directory (with user `node`)
|
||||
RUN mkdir -p /home/node/app
|
||||
|
||||
WORKDIR /home/node/app
|
||||
|
||||
# Install app dependencies
|
||||
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
||||
# where available (npm@5+)
|
||||
COPY --chown=node package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
# Bundle app source code
|
||||
COPY --chown=node . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
# Bind to all network interfaces so that it can be mapped to the host OS
|
||||
ENV HOST=0.0.0.0 PORT=3000
|
||||
|
||||
EXPOSE ${PORT}
|
||||
CMD [ "node", "." ]
|
||||
@@ -0,0 +1,3 @@
|
||||
# loopback-bookstore
|
||||
|
||||
[-@2x.png)](http://loopback.io/)
|
||||
+5183
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "loopback-bookstore",
|
||||
"version": "1.0.0",
|
||||
"description": "loopback-bookstore",
|
||||
"keywords": [
|
||||
"loopback-application",
|
||||
"loopback"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=10.16"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "lb-tsc",
|
||||
"build:watch": "lb-tsc --watch",
|
||||
"lint": "npm run prettier:check && npm run eslint",
|
||||
"lint:fix": "npm run eslint:fix && npm run prettier:fix",
|
||||
"prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"",
|
||||
"prettier:check": "npm run prettier:cli -- -l",
|
||||
"prettier:fix": "npm run prettier:cli -- --write",
|
||||
"eslint": "lb-eslint --report-unused-disable-directives .",
|
||||
"eslint:fix": "npm run eslint -- --fix",
|
||||
"pretest": "npm run clean && npm run build",
|
||||
"test": "lb-mocha --allow-console-logs \"dist/__tests__\"",
|
||||
"posttest": "npm run lint",
|
||||
"test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest",
|
||||
"docker:build": "docker build -t loopback-bookstore .",
|
||||
"docker:run": "docker run -p 3000:3000 -d loopback-bookstore",
|
||||
"migrate": "node ./dist/migrate",
|
||||
"openapi-spec": "node ./dist/openapi-spec",
|
||||
"prestart": "npm run build",
|
||||
"start": "node -r source-map-support/register .",
|
||||
"clean": "lb-clean dist *.tsbuildinfo .eslintcache"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "",
|
||||
"files": [
|
||||
"README.md",
|
||||
"dist",
|
||||
"src",
|
||||
"!*/__tests__"
|
||||
],
|
||||
"dependencies": {
|
||||
"@loopback/boot": "^2.3.5",
|
||||
"@loopback/core": "^2.9.1",
|
||||
"@loopback/openapi-v3": "^3.4.5",
|
||||
"@loopback/repository": "^2.9.0",
|
||||
"@loopback/rest": "^5.2.0",
|
||||
"@loopback/rest-explorer": "^2.2.6",
|
||||
"@loopback/service-proxy": "^2.3.4",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@loopback/build": "^6.1.0",
|
||||
"source-map-support": "^0.5.19",
|
||||
"@loopback/testlab": "^3.2.0",
|
||||
"@types/node": "^10.17.26",
|
||||
"@loopback/eslint-config": "^8.0.3",
|
||||
"eslint": "^7.3.1",
|
||||
"typescript": "~3.9.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>loopback-bookstore</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="https://loopback.io/favicon.ico">
|
||||
|
||||
<style>
|
||||
h3 {
|
||||
margin-left: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: #3f5dff;
|
||||
}
|
||||
|
||||
h3 a {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
a:hover, a:focus, a:active {
|
||||
color: #001956;
|
||||
}
|
||||
|
||||
.power {
|
||||
position: absolute;
|
||||
bottom: 25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.info {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%)
|
||||
}
|
||||
|
||||
.info h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info p {
|
||||
text-align: center;
|
||||
margin-bottom: 3em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: rgb(29, 30, 32);
|
||||
color: white;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: #4990e2;
|
||||
}
|
||||
|
||||
a:hover, a:focus, a:active {
|
||||
color: #2b78ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="info">
|
||||
<h1>loopback-bookstore</h1>
|
||||
<p>Version 1.0.0</p>
|
||||
|
||||
<h3>OpenAPI spec: <a href="/openapi.json">/openapi.json</a></h3>
|
||||
<h3>API Explorer: <a href="/explorer">/explorer</a></h3>
|
||||
</div>
|
||||
|
||||
<footer class="power">
|
||||
<a href="https://loopback.io" target="_blank">
|
||||
<img src="https://loopback.io/images/branding/powered-by-loopback/blue/powered-by-loopback-sm.png" />
|
||||
</a>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
# Tests
|
||||
|
||||
Please place your tests in this folder.
|
||||
@@ -0,0 +1,31 @@
|
||||
import {Client} from '@loopback/testlab';
|
||||
import {LoopbackBookstoreApplication} from '../..';
|
||||
import {setupApplication} from './test-helper';
|
||||
|
||||
describe('HomePage', () => {
|
||||
let app: LoopbackBookstoreApplication;
|
||||
let client: Client;
|
||||
|
||||
before('setupApplication', async () => {
|
||||
({app, client} = await setupApplication());
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.stop();
|
||||
});
|
||||
|
||||
it('exposes a default home page', async () => {
|
||||
await client
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /text\/html/);
|
||||
});
|
||||
|
||||
it('exposes self-hosted explorer', async () => {
|
||||
await client
|
||||
.get('/explorer/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /text\/html/)
|
||||
.expect(/<title>LoopBack API Explorer/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import {Client, expect} from '@loopback/testlab';
|
||||
import {LoopbackBookstoreApplication} from '../..';
|
||||
import {setupApplication} from './test-helper';
|
||||
|
||||
describe('PingController', () => {
|
||||
let app: LoopbackBookstoreApplication;
|
||||
let client: Client;
|
||||
|
||||
before('setupApplication', async () => {
|
||||
({app, client} = await setupApplication());
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await app.stop();
|
||||
});
|
||||
|
||||
it('invokes GET /ping', async () => {
|
||||
const res = await client.get('/ping?msg=world').expect(200);
|
||||
expect(res.body).to.containEql({greeting: 'Hello from LoopBack'});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import {LoopbackBookstoreApplication} from '../..';
|
||||
import {
|
||||
createRestAppClient,
|
||||
givenHttpServerConfig,
|
||||
Client,
|
||||
} from '@loopback/testlab';
|
||||
|
||||
export async function setupApplication(): Promise<AppWithClient> {
|
||||
const restConfig = givenHttpServerConfig({
|
||||
// Customize the server configuration here.
|
||||
// Empty values (undefined, '') will be ignored by the helper.
|
||||
//
|
||||
// host: process.env.HOST,
|
||||
// port: +process.env.PORT,
|
||||
});
|
||||
|
||||
const app = new LoopbackBookstoreApplication({
|
||||
rest: restConfig,
|
||||
});
|
||||
|
||||
await app.boot();
|
||||
await app.start();
|
||||
|
||||
const client = createRestAppClient(app);
|
||||
|
||||
return {app, client};
|
||||
}
|
||||
|
||||
export interface AppWithClient {
|
||||
app: LoopbackBookstoreApplication;
|
||||
client: Client;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {BootMixin} from '@loopback/boot';
|
||||
import {ApplicationConfig} from '@loopback/core';
|
||||
import {
|
||||
RestExplorerBindings,
|
||||
RestExplorerComponent,
|
||||
} from '@loopback/rest-explorer';
|
||||
import {RepositoryMixin} from '@loopback/repository';
|
||||
import {RestApplication} from '@loopback/rest';
|
||||
import {ServiceMixin} from '@loopback/service-proxy';
|
||||
import path from 'path';
|
||||
import {MySequence} from './sequence';
|
||||
|
||||
export {ApplicationConfig};
|
||||
|
||||
export class LoopbackBookstoreApplication extends BootMixin(
|
||||
ServiceMixin(RepositoryMixin(RestApplication)),
|
||||
) {
|
||||
constructor(options: ApplicationConfig = {}) {
|
||||
super(options);
|
||||
|
||||
// Set up the custom sequence
|
||||
this.sequence(MySequence);
|
||||
|
||||
// Set up default home page
|
||||
this.static('/', path.join(__dirname, '../public'));
|
||||
|
||||
// Customize @loopback/rest-explorer configuration here
|
||||
this.configure(RestExplorerBindings.COMPONENT).to({
|
||||
path: '/explorer',
|
||||
});
|
||||
this.component(RestExplorerComponent);
|
||||
|
||||
this.projectRoot = __dirname;
|
||||
// Customize @loopback/boot Booter Conventions here
|
||||
this.bootOptions = {
|
||||
controllers: {
|
||||
// Customize ControllerBooter Conventions here
|
||||
dirs: ['controllers'],
|
||||
extensions: ['.controller.js'],
|
||||
nested: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Controllers
|
||||
|
||||
This directory contains source files for the controllers exported by this app.
|
||||
|
||||
To add a new empty controller, type in `lb4 controller [<name>]` from the
|
||||
command-line of your application's root directory.
|
||||
|
||||
For more information, please visit
|
||||
[Controller generator](http://loopback.io/doc/en/lb4/Controller-generator.html).
|
||||
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
Count,
|
||||
CountSchema,
|
||||
Filter,
|
||||
FilterExcludingWhere,
|
||||
repository,
|
||||
Where,
|
||||
} from '@loopback/repository';
|
||||
import {
|
||||
post,
|
||||
param,
|
||||
get,
|
||||
getModelSchemaRef,
|
||||
patch,
|
||||
put,
|
||||
del,
|
||||
requestBody,
|
||||
} from '@loopback/rest';
|
||||
import {Book} from '../models';
|
||||
import {BookRepository} from '../repositories';
|
||||
|
||||
export class BooksController {
|
||||
constructor(
|
||||
@repository(BookRepository)
|
||||
public bookRepository : BookRepository,
|
||||
) {}
|
||||
|
||||
@post('/books', {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Book model instance',
|
||||
content: {'application/json': {schema: getModelSchemaRef(Book)}},
|
||||
},
|
||||
},
|
||||
})
|
||||
async create(
|
||||
@requestBody({
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(Book, {
|
||||
title: 'NewBook',
|
||||
exclude: ['id'],
|
||||
}),
|
||||
},
|
||||
},
|
||||
})
|
||||
book: Omit<Book, 'id'>,
|
||||
): Promise<Book> {
|
||||
return this.bookRepository.create(book);
|
||||
}
|
||||
|
||||
@get('/books/count', {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Book model count',
|
||||
content: {'application/json': {schema: CountSchema}},
|
||||
},
|
||||
},
|
||||
})
|
||||
async count(
|
||||
@param.where(Book) where?: Where<Book>,
|
||||
): Promise<Count> {
|
||||
return this.bookRepository.count(where);
|
||||
}
|
||||
|
||||
@get('/books', {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Array of Book model instances',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: getModelSchemaRef(Book, {includeRelations: true}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
async find(
|
||||
@param.filter(Book) filter?: Filter<Book>,
|
||||
): Promise<Book[]> {
|
||||
return this.bookRepository.find(filter);
|
||||
}
|
||||
|
||||
@patch('/books', {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Book PATCH success count',
|
||||
content: {'application/json': {schema: CountSchema}},
|
||||
},
|
||||
},
|
||||
})
|
||||
async updateAll(
|
||||
@requestBody({
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(Book, {partial: true}),
|
||||
},
|
||||
},
|
||||
})
|
||||
book: Book,
|
||||
@param.where(Book) where?: Where<Book>,
|
||||
): Promise<Count> {
|
||||
return this.bookRepository.updateAll(book, where);
|
||||
}
|
||||
|
||||
@get('/books/{id}', {
|
||||
responses: {
|
||||
'200': {
|
||||
description: 'Book model instance',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(Book, {includeRelations: true}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
async findById(
|
||||
@param.path.number('id') id: number,
|
||||
@param.filter(Book, {exclude: 'where'}) filter?: FilterExcludingWhere<Book>
|
||||
): Promise<Book> {
|
||||
return this.bookRepository.findById(id, filter);
|
||||
}
|
||||
|
||||
@patch('/books/{id}', {
|
||||
responses: {
|
||||
'204': {
|
||||
description: 'Book PATCH success',
|
||||
},
|
||||
},
|
||||
})
|
||||
async updateById(
|
||||
@param.path.number('id') id: number,
|
||||
@requestBody({
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(Book, {partial: true}),
|
||||
},
|
||||
},
|
||||
})
|
||||
book: Book,
|
||||
): Promise<void> {
|
||||
await this.bookRepository.updateById(id, book);
|
||||
}
|
||||
|
||||
@put('/books/{id}', {
|
||||
responses: {
|
||||
'204': {
|
||||
description: 'Book PUT success',
|
||||
},
|
||||
},
|
||||
})
|
||||
async replaceById(
|
||||
@param.path.number('id') id: number,
|
||||
@requestBody() book: Book,
|
||||
): Promise<void> {
|
||||
await this.bookRepository.replaceById(id, book);
|
||||
}
|
||||
|
||||
@del('/books/{id}', {
|
||||
responses: {
|
||||
'204': {
|
||||
description: 'Book DELETE success',
|
||||
},
|
||||
},
|
||||
})
|
||||
async deleteById(@param.path.number('id') id: number): Promise<void> {
|
||||
await this.bookRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './ping.controller';
|
||||
export * from './books.controller';
|
||||
@@ -0,0 +1,52 @@
|
||||
import {Request, RestBindings, get, ResponseObject} from '@loopback/rest';
|
||||
import {inject} from '@loopback/core';
|
||||
|
||||
/**
|
||||
* OpenAPI response for ping()
|
||||
*/
|
||||
const PING_RESPONSE: ResponseObject = {
|
||||
description: 'Ping Response',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
title: 'PingResponse',
|
||||
properties: {
|
||||
greeting: {type: 'string'},
|
||||
date: {type: 'string'},
|
||||
url: {type: 'string'},
|
||||
headers: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'Content-Type': {type: 'string'},
|
||||
},
|
||||
additionalProperties: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A simple controller to bounce back http requests
|
||||
*/
|
||||
export class PingController {
|
||||
constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {}
|
||||
|
||||
// Map to `GET /ping`
|
||||
@get('/ping', {
|
||||
responses: {
|
||||
'200': PING_RESPONSE,
|
||||
},
|
||||
})
|
||||
ping(): object {
|
||||
// Reply with a greeting, the current time, the url, and request headers
|
||||
return {
|
||||
greeting: 'Hello from LoopBack',
|
||||
date: new Date(),
|
||||
url: this.req.url,
|
||||
headers: Object.assign({}, this.req.headers),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Datasources
|
||||
|
||||
This directory contains config for datasources used by this app.
|
||||
@@ -0,0 +1 @@
|
||||
export * from './local.datasource';
|
||||
@@ -0,0 +1,27 @@
|
||||
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
|
||||
import {juggler} from '@loopback/repository';
|
||||
|
||||
const config = {
|
||||
name: 'local',
|
||||
connector: 'memory',
|
||||
localStorage: '',
|
||||
file: ''
|
||||
};
|
||||
|
||||
// Observe application's life cycle to disconnect the datasource when
|
||||
// application is stopped. This allows the application to be shut down
|
||||
// gracefully. The `stop()` method is inherited from `juggler.DataSource`.
|
||||
// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html
|
||||
@lifeCycleObserver('datasource')
|
||||
export class LocalDataSource extends juggler.DataSource
|
||||
implements LifeCycleObserver {
|
||||
static dataSourceName = 'local';
|
||||
static readonly defaultConfig = config;
|
||||
|
||||
constructor(
|
||||
@inject('datasources.config.local', {optional: true})
|
||||
dsConfig: object = config,
|
||||
) {
|
||||
super(dsConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {ApplicationConfig, LoopbackBookstoreApplication} from './application';
|
||||
|
||||
export * from './application';
|
||||
|
||||
export async function main(options: ApplicationConfig = {}) {
|
||||
const app = new LoopbackBookstoreApplication(options);
|
||||
await app.boot();
|
||||
await app.start();
|
||||
|
||||
const url = app.restServer.url;
|
||||
console.log(`Server is running at ${url}`);
|
||||
console.log(`Try ${url}/ping`);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
// Run the application
|
||||
const config = {
|
||||
rest: {
|
||||
port: +(process.env.PORT ?? 3000),
|
||||
host: process.env.HOST,
|
||||
// The `gracePeriodForClose` provides a graceful close for http/https
|
||||
// servers with keep-alive clients. The default value is `Infinity`
|
||||
// (don't force-close). If you want to immediately destroy all sockets
|
||||
// upon stop, set its value to `0`.
|
||||
// See https://www.npmjs.com/package/stoppable
|
||||
gracePeriodForClose: 5000, // 5 seconds
|
||||
openApiSpec: {
|
||||
// useful when used with OpenAPI-to-GraphQL to locate your application
|
||||
setServersFromRequest: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
main(config).catch(err => {
|
||||
console.error('Cannot start the application.', err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {LoopbackBookstoreApplication} from './application';
|
||||
|
||||
export async function migrate(args: string[]) {
|
||||
const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter';
|
||||
console.log('Migrating schemas (%s existing schema)', existingSchema);
|
||||
|
||||
const app = new LoopbackBookstoreApplication();
|
||||
await app.boot();
|
||||
await app.migrateSchema({existingSchema});
|
||||
|
||||
// Connectors usually keep a pool of opened connections,
|
||||
// this keeps the process running even after all work is done.
|
||||
// We need to exit explicitly.
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
migrate(process.argv).catch(err => {
|
||||
console.error('Cannot migrate database schema', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
# Models
|
||||
|
||||
This directory contains code for models provided by this app.
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Entity, model, property} from '@loopback/repository';
|
||||
|
||||
@model()
|
||||
export class Book extends Entity {
|
||||
@property({
|
||||
type: 'number',
|
||||
id: true,
|
||||
generated: true,
|
||||
})
|
||||
id?: number;
|
||||
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
title: string;
|
||||
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
})
|
||||
author: string;
|
||||
|
||||
|
||||
constructor(data?: Partial<Book>) {
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
|
||||
export interface BookRelations {
|
||||
// describe navigational properties here
|
||||
}
|
||||
|
||||
export type BookWithRelations = Book & BookRelations;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './book.model';
|
||||
@@ -0,0 +1,23 @@
|
||||
import {ApplicationConfig} from '@loopback/core';
|
||||
import {LoopbackBookstoreApplication} from './application';
|
||||
|
||||
/**
|
||||
* Export the OpenAPI spec from the application
|
||||
*/
|
||||
async function exportOpenApiSpec(): Promise<void> {
|
||||
const config: ApplicationConfig = {
|
||||
rest: {
|
||||
port: +(process.env.PORT ?? 3000),
|
||||
host: process.env.HOST ?? 'localhost',
|
||||
},
|
||||
};
|
||||
const outFile = process.argv[2] ?? '';
|
||||
const app = new LoopbackBookstoreApplication(config);
|
||||
await app.boot();
|
||||
await app.exportOpenApiSpec(outFile);
|
||||
}
|
||||
|
||||
exportOpenApiSpec().catch(err => {
|
||||
console.error('Fail to export OpenAPI spec from the application.', err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
# Repositories
|
||||
|
||||
This directory contains code for repositories provided by this app.
|
||||
@@ -0,0 +1,16 @@
|
||||
import {DefaultCrudRepository} from '@loopback/repository';
|
||||
import {Book, BookRelations} from '../models';
|
||||
import {LocalDataSource} from '../datasources';
|
||||
import {inject} from '@loopback/core';
|
||||
|
||||
export class BookRepository extends DefaultCrudRepository<
|
||||
Book,
|
||||
typeof Book.prototype.id,
|
||||
BookRelations
|
||||
> {
|
||||
constructor(
|
||||
@inject('datasources.local') dataSource: LocalDataSource,
|
||||
) {
|
||||
super(Book, dataSource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './book.repository';
|
||||
@@ -0,0 +1,45 @@
|
||||
import {inject} from '@loopback/core';
|
||||
import {
|
||||
FindRoute,
|
||||
InvokeMethod,
|
||||
InvokeMiddleware,
|
||||
ParseParams,
|
||||
Reject,
|
||||
RequestContext,
|
||||
RestBindings,
|
||||
Send,
|
||||
SequenceHandler,
|
||||
} from '@loopback/rest';
|
||||
|
||||
const SequenceActions = RestBindings.SequenceActions;
|
||||
|
||||
export class MySequence implements SequenceHandler {
|
||||
/**
|
||||
* Optional invoker for registered middleware in a chain.
|
||||
* To be injected via SequenceActions.INVOKE_MIDDLEWARE.
|
||||
*/
|
||||
@inject(SequenceActions.INVOKE_MIDDLEWARE, {optional: true})
|
||||
protected invokeMiddleware: InvokeMiddleware = () => false;
|
||||
|
||||
constructor(
|
||||
@inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
|
||||
@inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
|
||||
@inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
|
||||
@inject(SequenceActions.SEND) public send: Send,
|
||||
@inject(SequenceActions.REJECT) public reject: Reject,
|
||||
) {}
|
||||
|
||||
async handle(context: RequestContext) {
|
||||
try {
|
||||
const {request, response} = context;
|
||||
const finished = await this.invokeMiddleware(context);
|
||||
if (finished) return;
|
||||
const route = this.findRoute(request);
|
||||
const args = await this.parseParams(request, route);
|
||||
const result = await this.invoke(route, args);
|
||||
this.send(response, result);
|
||||
} catch (err) {
|
||||
this.reject(context, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"extends": "@loopback/build/config/tsconfig.common.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user