2020-05-13

Class-based local storage

Class-based local storage

Sometimes when I was trying to build some small app, itโ€™s really not neccessary to connect the app to the cloud database like mongoDB.

A local json file would be enough.

It provide basic CURD api. And it also allows customed repo by extending a new class from it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//using node modules
const fs = require('fs');
const crypto = require('crypto');

module.exports = class Repository {
constructor(filename) {
if (!filename) { //filename validator
throw new Error('Creating a repository requires a filename');
}

this.filename = filename;
try { //if the repo exists, access it
fs.accessSync(this.filename);
} catch (err) { //if the repo doesn't exist, create one
fs.writeFileSync(this.filename, '[]');
}
}

async create(attrs) { //cerate new data entry
attrs.id = this.randomId();
const records = await this.getAll();
records.push(attrs);
await this.writeAll(records);
return attrs;
}

async getAll() { //get all data
return JSON.parse(
await fs.promises.readFile(this.filename, {
encoding: 'utf8'
})
);
}
async writeAll(records) { //write file
await fs.promises.writeFile(
this.filename,
JSON.stringify(records, null, 2)
);
}
randomId() { //create an uniq id for each entry
return crypto.randomBytes(4).toString('hex');
}

async getOne(id) { //get single data
const records = await this.getAll();
return records.find(record => record.id === id);
}

async delete(id) { //delete
const records = await this.getAll();
const filteredRecords = records.filter(record => record.id !== id);
await this.writeAll(filteredRecords);
}

async update(id, attrs) { //update data
const records = await this.getAll();
const record = records.find(record => record.id === id);

if (!record) {
throw new Error(`Record with id ${id} not found`);
}
Object.assign(record, attrs);
await this.writeAll(records);
}

async getOneBy(filters) { //filter data
const records = await this.getAll();
for (let record of records) {
let found = true;
for (let key in filters) {
if (record[key] !== filters[key]) {
found = false;
}
}
if (found) {
return record;
}
}
}
};

A user repo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const fs = require('fs');
const crypto = require('crypto');
const util = require('util');
const Repository = require('./repository');
//require the base repo

const scrypt = util.promisify(crypto.scrypt);

class UsersRepository extends Repository {
async comparePasswords(saved, supplied) {
// Saved -> password saved in our database. 'hashed.salt'
// Supplied -> password given to us by a user trying sign in
const [hashed, salt] = saved.split('.');
const hashedSuppliedBuf = await scrypt(supplied, salt, 64);

return hashed === hashedSuppliedBuf.toString('hex');
}

async create(attrs) {
attrs.id = this.randomId();

const salt = crypto.randomBytes(8).toString('hex');
const buf = await scrypt(attrs.password, salt, 64);

const records = await this.getAll();
const record = {
...attrs,
password: `${buf.toString('hex')}.${salt}`
};
records.push(record);

await this.writeAll(records);

return record;
}
}

module.exports = new UsersRepository('users.json');
//directly export the instance to aviod creating new repo accidently

Ref:

Stephen Grider โ€˜s udemy course