Manipulação de SQL
Arquivos
monitor-app-db-simple
├── back
│ ├── package-lock.json
│ ├── package.json
│ ├── requests.http
│ └── src
│ ├── database
│ │ ├── database.js
│ │ ├── db.sqlite
│ │ ├── load.js
│ │ ├── migration.js
│ │ ├── seeders.js
│ │ └── seeders.json
│ ├── index.js
│ ├── models
│ │ └── Hosts.js
│ ├── routes.js
│ └── routes.test.js
└── front
├── css
│ └── style.css
├── index.html
├── js
│ ├── components
│ │ ├── HostForm.js
│ │ ├── HostTableRow.js
│ │ └── Modal.js
│ ├── lib
│ │ ├── dom.js
│ │ └── hosts.js
│ ├── main.js
│ └── services
│ └── storage.js
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
└── vite.config.js
Arquivos
monitor-app-db-simple
├── back
│ ├── package-lock.json
│ ├── package.json
│ ├── requests.http
│ └── src
│ ├── database
│ │ ├── database.js
│ │ ├── db.sqlite
│ │ ├── load.js
│ │ ├── migration.js
│ │ ├── seeders.js
│ │ └── seeders.json
│ ├── index.js
│ ├── models
│ │ └── Hosts.js
│ ├── routes.js
│ └── routes.test.js
└── front
├── css
│ └── style.css
├── index.html
├── js
│ ├── components
│ │ ├── HostForm.js
│ │ ├── HostTableRow.js
│ │ └── Modal.js
│ ├── lib
│ │ ├── dom.js
│ │ └── hosts.js
│ ├── main.js
│ └── services
│ └── storage.js
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
└── vite.config.js
Migration
/codes/expressjs/monitor-app-db-simple/back/src/database/database.js
import { resolve } from 'node:path';
import { Database } from 'sqlite-async';
const dbFile = resolve('src', 'database', 'db.sqlite');
async function connect() {
return await Database.open(dbFile);
}
export default { connect };
/codes/expressjs/monitor-app-db-simple/back/src/database/database.js
import { resolve } from 'node:path';
import { Database } from 'sqlite-async';
const dbFile = resolve('src', 'database', 'db.sqlite');
async function connect() {
return await Database.open(dbFile);
}
export default { connect };
$ npm install sqlite-async
$ npm remove uuidv4
$ npm install sqlite-async
$ npm remove uuidv4
/codes/expressjs/monitor-app-db-simple/back/src/database/migration.js
import Database from './database.js';
async function up() {
const db = await Database.connect();
const hostsSql = `
CREATE TABLE hosts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
address VARCHAR(100) NOT NULL
)
`;
await db.run(hostsSql);
db.close();
}
async function down() {
const db = await Database.connect();
const hostsSql = `
DROP TABLE hosts
`;
await db.run(hostsSql);
db.close();
}
export default { up, down };
/codes/expressjs/monitor-app-db-simple/back/src/database/migration.js
import Database from './database.js';
async function up() {
const db = await Database.connect();
const hostsSql = `
CREATE TABLE hosts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
address VARCHAR(100) NOT NULL
)
`;
await db.run(hostsSql);
db.close();
}
async function down() {
const db = await Database.connect();
const hostsSql = `
DROP TABLE hosts
`;
await db.run(hostsSql);
db.close();
}
export default { up, down };
Tabela hosts
:
/codes/expressjs/monitor-app-db-simple/back/src/database/load.js
import Migration from './migration.js';
import Seed from './seeders.js';
async function load() {
await Migration.up();
await Seed.up();
}
load();
/codes/expressjs/monitor-app-db-simple/back/src/database/load.js
import Migration from './migration.js';
import Seed from './seeders.js';
async function load() {
await Migration.up();
await Seed.up();
}
load();
/codes/expressjs/monitor-app-db-simple/back/package.json
{
"name": "invest-app",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"db:reload": "npm run db:drop && npm run db:load",
"db:load": "node src/database/load.js",
"db:drop": "[ -e src/database/db.sqlite ] && rm src/database/db.sqlite",
"test": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src",
"test:coverage": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
},
"jest": {
"collectCoverage": true,
"testTimeout": 20000,
"coverageReporters": [
"json",
"html"
]
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"morgan": "^1.10.0",
"sqlite-async": "^1.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^6.3.4"
}
}
/codes/expressjs/monitor-app-db-simple/back/package.json
{
"name": "invest-app",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"db:reload": "npm run db:drop && npm run db:load",
"db:load": "node src/database/load.js",
"db:drop": "[ -e src/database/db.sqlite ] && rm src/database/db.sqlite",
"test": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src",
"test:coverage": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
},
"jest": {
"collectCoverage": true,
"testTimeout": 20000,
"coverageReporters": [
"json",
"html"
]
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"morgan": "^1.10.0",
"sqlite-async": "^1.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^6.3.4"
}
}
$ npm run db:load
$ npm run db:reload
$ npm run db:drop
$ npm run db:load
$ npm run db:reload
$ npm run db:drop
Para visualizar o db.sqlite use o plugin vscode-sqlite ou sqlite-viewer.
Model
/codes/expressjs/monitor-app-db-simple/back/src/models/Hosts.js
import Database from '../database/database.js';
async function create({ name, address }) {
const db = await Database.connect();
const sql = `
INSERT INTO
hosts (name, address)
VALUES
(?, ?)
`;
const { lastID } = await db.run(sql, [name, address]);
db.close();
return await readById(lastID);
}
async function read(where) {
const db = await Database.connect();
if (where) {
const field = Object.keys(where)[0];
const value = where[field];
const sql = `
SELECT
*
FROM
hosts
WHERE
${field} LIKE CONCAT( '%',?,'%')
`;
const hosts = await db.all(sql, [value]);
db.close();
return hosts;
}
const sql = `
SELECT
*
FROM
hosts
`;
const hosts = await db.all(sql);
db.close();
return hosts;
}
async function readById(id) {
const db = await Database.connect();
const sql = `
SELECT
*
FROM
hosts
WHERE
id = ?
`;
const host = await db.get(sql, [id]);
db.close();
return host;
}
async function update({ id, name, address }) {
const db = await Database.connect();
const sql = `
UPDATE
hosts
SET
name = ?, address = ?
WHERE
id = ?
`;
const { changes } = await db.run(sql, [name, address, id]);
db.close();
if (changes === 1) {
return readById(id);
} else {
throw new Error('Host not found');
}
}
async function remove(id) {
const db = await Database.connect();
const sql = `
DELETE FROM
hosts
WHERE
id = ?
`;
const { changes } = await db.run(sql, [id]);
db.close();
if (changes === 1) {
return true;
} else {
throw new Error('Host not found');
}
}
export default { create, read, readById, update, remove };
/codes/expressjs/monitor-app-db-simple/back/src/models/Hosts.js
import Database from '../database/database.js';
async function create({ name, address }) {
const db = await Database.connect();
const sql = `
INSERT INTO
hosts (name, address)
VALUES
(?, ?)
`;
const { lastID } = await db.run(sql, [name, address]);
db.close();
return await readById(lastID);
}
async function read(where) {
const db = await Database.connect();
if (where) {
const field = Object.keys(where)[0];
const value = where[field];
const sql = `
SELECT
*
FROM
hosts
WHERE
${field} LIKE CONCAT( '%',?,'%')
`;
const hosts = await db.all(sql, [value]);
db.close();
return hosts;
}
const sql = `
SELECT
*
FROM
hosts
`;
const hosts = await db.all(sql);
db.close();
return hosts;
}
async function readById(id) {
const db = await Database.connect();
const sql = `
SELECT
*
FROM
hosts
WHERE
id = ?
`;
const host = await db.get(sql, [id]);
db.close();
return host;
}
async function update({ id, name, address }) {
const db = await Database.connect();
const sql = `
UPDATE
hosts
SET
name = ?, address = ?
WHERE
id = ?
`;
const { changes } = await db.run(sql, [name, address, id]);
db.close();
if (changes === 1) {
return readById(id);
} else {
throw new Error('Host not found');
}
}
async function remove(id) {
const db = await Database.connect();
const sql = `
DELETE FROM
hosts
WHERE
id = ?
`;
const { changes } = await db.run(sql, [id]);
db.close();
if (changes === 1) {
return true;
} else {
throw new Error('Host not found');
}
}
export default { create, read, readById, update, remove };
Router
/codes/expressjs/monitor-app-db-simple/back/src/routes.js
import express from 'express';
import Host from './models/Hosts.js';
class HttpError extends Error {
constructor(message, code = 400) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post('/hosts', async (req, res) => {
const { name, address } = req.body;
if (!name || !address) {
throw new HttpError('Error when passing parameters');
}
try {
const createdHost = await Host.create({ name, address });
return res.status(201).json(createdHost);
} catch (error) {
throw new HttpError('Unable to create a host');
}
});
router.get('/hosts', async (req, res) => {
const { name } = req.query;
try {
if (name) {
const filteredHosts = await Host.read({ name });
return res.json(filteredHosts);
}
const hosts = await Host.read();
return res.json(hosts);
} catch (error) {
throw new HttpError('Unable to read hosts');
}
});
router.get('/hosts/:id', async (req, res) => {
const { id } = req.params;
try {
const host = await Host.readById(id);
if (host) {
return res.json(host);
} else {
throw new HttpError('Host not found');
}
} catch (error) {
throw new HttpError('Unable to read a host');
}
});
router.put('/hosts/:id', async (req, res) => {
const { name, address } = req.body;
const id = req.params.id;
if (!name || !address) {
throw new HttpError('Error when passing parameters');
}
try {
const updatedHost = await Host.update({ id, name, address });
return res.json(updatedHost);
} catch (error) {
throw new HttpError('Unable to update a host');
}
});
router.delete('/hosts/:id', async (req, res) => {
const { id } = req.params;
try {
await Host.remove(id);
return res.send(204);
} catch (error) {
throw new HttpError('Unable to delete a host');
}
});
// 404 handler
router.use((req, res, next) => {
return res.status(404).json({ message: 'Content not found!' });
});
// Error handler
router.use((err, req, res, next) => {
// console.error(err.message);
console.error(err.stack);
if (err instanceof HttpError) {
return res.status(err.code).json({ message: err.message });
}
// next(err);
return res.status(500).json({ message: 'Something broke!' });
});
export default router;
/codes/expressjs/monitor-app-db-simple/back/src/routes.js
import express from 'express';
import Host from './models/Hosts.js';
class HttpError extends Error {
constructor(message, code = 400) {
super(message);
this.code = code;
}
}
const router = express.Router();
router.post('/hosts', async (req, res) => {
const { name, address } = req.body;
if (!name || !address) {
throw new HttpError('Error when passing parameters');
}
try {
const createdHost = await Host.create({ name, address });
return res.status(201).json(createdHost);
} catch (error) {
throw new HttpError('Unable to create a host');
}
});
router.get('/hosts', async (req, res) => {
const { name } = req.query;
try {
if (name) {
const filteredHosts = await Host.read({ name });
return res.json(filteredHosts);
}
const hosts = await Host.read();
return res.json(hosts);
} catch (error) {
throw new HttpError('Unable to read hosts');
}
});
router.get('/hosts/:id', async (req, res) => {
const { id } = req.params;
try {
const host = await Host.readById(id);
if (host) {
return res.json(host);
} else {
throw new HttpError('Host not found');
}
} catch (error) {
throw new HttpError('Unable to read a host');
}
});
router.put('/hosts/:id', async (req, res) => {
const { name, address } = req.body;
const id = req.params.id;
if (!name || !address) {
throw new HttpError('Error when passing parameters');
}
try {
const updatedHost = await Host.update({ id, name, address });
return res.json(updatedHost);
} catch (error) {
throw new HttpError('Unable to update a host');
}
});
router.delete('/hosts/:id', async (req, res) => {
const { id } = req.params;
try {
await Host.remove(id);
return res.send(204);
} catch (error) {
throw new HttpError('Unable to delete a host');
}
});
// 404 handler
router.use((req, res, next) => {
return res.status(404).json({ message: 'Content not found!' });
});
// Error handler
router.use((err, req, res, next) => {
// console.error(err.message);
console.error(err.stack);
if (err instanceof HttpError) {
return res.status(err.code).json({ message: err.message });
}
// next(err);
return res.status(500).json({ message: 'Something broke!' });
});
export default router;
/codes/expressjs/monitor-app-db-simple/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server",
"address": "1.1.1.1"
}
### Create a host without name or address
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server"
}
### Read hosts
GET {{server}}/hosts
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
### Read a host by id with invalid id
GET {{server}}/hosts/x
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
{
"name": "Cloudflare DNS",
"address": "1.1.1.1"
}
### Update a host without name or address
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
{
"name": "Cloudflare DNS"
}
### Update a host with invalid id
PUT {{server}}/hosts/x
Content-Type: application/json
{
"name": "Cloudflare DNS",
"address": "1.1.1.1"
}
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
### Delete a host with invalid id
DELETE {{server}}/hosts/x
/codes/expressjs/monitor-app-db-simple/back/requests.http
@server=http://localhost:3000
@createdHostId = {{createHost.response.body.$.id}}
### Create a host
# @name createHost
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server",
"address": "1.1.1.1"
}
### Create a host without name or address
POST {{server}}/hosts
Content-Type: application/json
{
"name": "DNS Server"
}
### Read hosts
GET {{server}}/hosts
### Read a host by name
GET {{server}}/hosts?name=Google%20DNS
# GET {{server}}/hosts?name=DNS
# GET {{server}}/hosts?name=dns
### Read a host by id
GET {{server}}/hosts/{{createdHostId}}
### Read a host by id with invalid id
GET {{server}}/hosts/x
### Update a host
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
{
"name": "Cloudflare DNS",
"address": "1.1.1.1"
}
### Update a host without name or address
PUT {{server}}/hosts/{{createdHostId}}
Content-Type: application/json
{
"name": "Cloudflare DNS"
}
### Update a host with invalid id
PUT {{server}}/hosts/x
Content-Type: application/json
{
"name": "Cloudflare DNS",
"address": "1.1.1.1"
}
### Delete a host
DELETE {{server}}/hosts/{{createdHostId}}
### Delete a host with invalid id
DELETE {{server}}/hosts/x
Teste
$ npm i jest supertest -D
$ npm run test
$ npm i jest supertest -D
$ npm run test
/codes/expressjs/monitor-app-db-simple/back/src/routes.test.js
import request from 'supertest';
import app from './index.js';
let createdHost;
const newHost = {
name: 'DNS Server',
address: '1.1.1.1',
};
const updatedHost = {
name: 'Cloudflare DNS',
address: '1.1.1.1',
};
describe('Moniotr App', () => {
describe('Hosts Endpoints', () => {
describe('POST /hosts', () => {
it('should create a new host', async () => {
const response = await request(app).post('/hosts').send(newHost);
createdHost = response.body;
expect(response.statusCode).toBe(201);
});
it('should not create a new host without name or address', async () => {
const response = await request(app).post('/hosts').send({
name: 'DNS Server',
});
expect(response.statusCode).toBe(400);
});
});
describe('GET /hosts', () => {
it('should show all hosts', async () => {
const response = await request(app).get('/hosts');
expect(response.statusCode).toBe(200);
});
it('should list the valid host', async () => {
const response = await request(app).get('/hosts');
const hasValidHost = response.body.some(
(host) => host.address === createdHost.address
);
expect(hasValidHost).toBeTruthy();
});
it('should show all hosts by name', async () => {
const response = await request(app).get('/hosts?name=DNS');
expect(response.statusCode).toBe(200);
});
});
describe('GET /hosts/:hostId', () => {
it('should show a host by id', async () => {
const response = await request(app).get(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(200);
expect(response.body.name).toBe(createdHost.name);
});
it('should not show a host with invalid id', async () => {
const response = await request(app).get(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to read a host');
});
});
describe('PUT /hosts/:hostId', () => {
it('should update a host', async () => {
const response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send(updatedHost);
expect(response.statusCode).toBe(200);
});
it('should list an updated host', async () => {
const response = await request(app).get('/hosts');
const hasValidHost = response.body.some(
(host) => host.address === updatedHost.address
);
expect(hasValidHost).toBeTruthy();
});
it('should not update a host without name or address', async () => {
const response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send({
name: 'Cloudflare DNS',
});
expect(response.statusCode).toBe(400);
});
it('should not update a host with invalid id', async () => {
const response = await request(app).put(`/hosts/x`).send(updatedHost);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to update a host');
});
});
describe('DELETE /hosts/:hostId', () => {
it('should remove a host', async () => {
const response = await request(app).delete(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(204);
});
it('should not delete a host with invalid id', async () => {
const response = await request(app).delete(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to delete a host');
});
});
});
});
/codes/expressjs/monitor-app-db-simple/back/src/routes.test.js
import request from 'supertest';
import app from './index.js';
let createdHost;
const newHost = {
name: 'DNS Server',
address: '1.1.1.1',
};
const updatedHost = {
name: 'Cloudflare DNS',
address: '1.1.1.1',
};
describe('Moniotr App', () => {
describe('Hosts Endpoints', () => {
describe('POST /hosts', () => {
it('should create a new host', async () => {
const response = await request(app).post('/hosts').send(newHost);
createdHost = response.body;
expect(response.statusCode).toBe(201);
});
it('should not create a new host without name or address', async () => {
const response = await request(app).post('/hosts').send({
name: 'DNS Server',
});
expect(response.statusCode).toBe(400);
});
});
describe('GET /hosts', () => {
it('should show all hosts', async () => {
const response = await request(app).get('/hosts');
expect(response.statusCode).toBe(200);
});
it('should list the valid host', async () => {
const response = await request(app).get('/hosts');
const hasValidHost = response.body.some(
(host) => host.address === createdHost.address
);
expect(hasValidHost).toBeTruthy();
});
it('should show all hosts by name', async () => {
const response = await request(app).get('/hosts?name=DNS');
expect(response.statusCode).toBe(200);
});
});
describe('GET /hosts/:hostId', () => {
it('should show a host by id', async () => {
const response = await request(app).get(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(200);
expect(response.body.name).toBe(createdHost.name);
});
it('should not show a host with invalid id', async () => {
const response = await request(app).get(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to read a host');
});
});
describe('PUT /hosts/:hostId', () => {
it('should update a host', async () => {
const response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send(updatedHost);
expect(response.statusCode).toBe(200);
});
it('should list an updated host', async () => {
const response = await request(app).get('/hosts');
const hasValidHost = response.body.some(
(host) => host.address === updatedHost.address
);
expect(hasValidHost).toBeTruthy();
});
it('should not update a host without name or address', async () => {
const response = await request(app)
.put(`/hosts/${createdHost.id}`)
.send({
name: 'Cloudflare DNS',
});
expect(response.statusCode).toBe(400);
});
it('should not update a host with invalid id', async () => {
const response = await request(app).put(`/hosts/x`).send(updatedHost);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to update a host');
});
});
describe('DELETE /hosts/:hostId', () => {
it('should remove a host', async () => {
const response = await request(app).delete(`/hosts/${createdHost.id}`);
expect(response.statusCode).toBe(204);
});
it('should not delete a host with invalid id', async () => {
const response = await request(app).delete(`/hosts/x`);
expect(response.statusCode).toBe(400);
expect(response.body.message).toBe('Unable to delete a host');
});
});
});
});
.skip()
describe.skip()
it.skip()
describe.skip()
it.skip()
Cobertura de Testes
/codes/expressjs/monitor-app-db-simple/back/package.json
{
"name": "invest-app",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"db:reload": "npm run db:drop && npm run db:load",
"db:load": "node src/database/load.js",
"db:drop": "[ -e src/database/db.sqlite ] && rm src/database/db.sqlite",
"test": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src",
"test:coverage": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
},
"jest": {
"collectCoverage": true,
"testTimeout": 20000,
"coverageReporters": [
"json",
"html"
]
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"morgan": "^1.10.0",
"sqlite-async": "^1.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^6.3.4"
}
}
/codes/expressjs/monitor-app-db-simple/back/package.json
{
"name": "invest-app",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "node --watch src/index.js",
"db:reload": "npm run db:drop && npm run db:load",
"db:load": "node src/database/load.js",
"db:drop": "[ -e src/database/db.sqlite ] && rm src/database/db.sqlite",
"test": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src",
"test:coverage": "npm run db:reload && node --experimental-vm-modules ./node_modules/.bin/jest src --coverage"
},
"jest": {
"collectCoverage": true,
"testTimeout": 20000,
"coverageReporters": [
"json",
"html"
]
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"morgan": "^1.10.0",
"sqlite-async": "^1.2.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^6.3.4"
}
}
$ npm run test:coverage
$ npm run test:coverage