Before learning about relational databases and using SQL (structured query language), we're going to learn about flat-file databases and reading/writing files via Bun/JavaScript. In particular, we're going to look at spreadsheets, CSVs (comma separated values) and JSON (JavaScript object notation). First, some vocabulary:
Relational databases have defined relationships between tables, but flat-file databases don't - they only have one table.
One way we can store data is with a file for each table in which each record is on a different line, using characters like commas to separate fields. If our data includes a comma, we could enclose the whole field with "quotes" (but what if we want to include a quote in our field?!). We can optionally use the first row as column headings.
We could start with a spreadsheet (eg using Excel), put some data in then 'save as' a CSV.
data.csv (as output from Excel):
forename,surname,dob,notes
john,smith,15/04/2007,John is a bit smelly!
alice,baker,20/05/2007,"Alice, where do I start?"
Then we can read that file into JavaScript using Bun.
let file = 'data.csv' // ensure data.csv is in the same folder
let records = []
let data = await Bun.file(file).text()
for (let record of data.trim().split(/\r?\n/)) {
records.push(record.split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/).map(v => v.replace(/"/g, '')))
}
console.log(records)
... that code is quite complex (what on earth is .split(/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/) doing, for example?!). Also, CSVs come in all kinds of weird varieties too (eg sometimes actually separating on tabs instead of commas). My advice: don't use them, and use JSON instead. If you do have to use them, find a tried and tested CSV reading/writing library you can import rather than attempting to write your own.
Your turn:
.csvJSON to the rescue! We looked at JSON briefly before, and then used it for HTTP clients and servers.
JSON is a way to serialise JavaScript variables - objects, arrays, numbers and strings. Files can be created holding JSON data, making it a portable data format which can be used in JavaScript as well as other languages. It's partly an alternative to CSVs and XML (extensible markup language).
Here's how we can use it:
// Here's a JavaScript object holding various things
let stuff = {
thing: 'Apples', // a string
blah: 53, // a number
items: [3, 'a', 'b'], // an array
people: { bob: 'yellow', ringo: 'green' } // an object
}
// Here we're turning it into JSON as a string and outputting it
console.log(JSON.stringify(stuff))
Running the above code will output {"thing":"Apples","blah":53,"items":[3,"a","b"],"people":{"bob":"yellow","ringo":"green"}} to the console.
The string that was output looks a lot like the stuff object we created, but crucially it has been turned into a string (also "quotes" have been added and some whitespace and all comments removed). We could store that string to a file:
let stuff = {
thing: 'Apples',
blah: 53,
items: [3, 'a', 'b'],
people: { bob: 'yellow', ringo: 'green' }
}
await Bun.write('output.json', JSON.stringify(stuff))
Run that code, and you should see a new file appear in the same folder called output.json - have a look inside to find the contents of the stuff object.
Your turn:
stuff above)JSON.stringify() on your object
let stuff = { func: () => 3 }?NOTE: the JS keyword
awaitis used to wait for promises to be fulfilled; promises are used when a method could take time (eg file handling or network requests), and you don't want to block the main thread.
This time, we're going to read the JSON file we created, and parse the string back into an object.
let text = await Bun.file('output.json').text()
console.log(text)
This will output {"thing":"Apples","blah":53,"items":[3,"a","b"],"people":{"bob":"yellow","ringo":"green"}} to the console again. But if we want to use that data in JavaScript, we need to parse it into an object first:
let text = await Bun.file('output.json').text()
let data = JSON.parse(text)
data.thing = 'Bananas' // edit a part of the object
console.log(data)
This time, it will output the editable object rather than just a string of the data. In Bun, as well as.text() we also have .json() which will do the parsing for us:
let data = await Bun.file('output.json').json() // .json() instead of .text()
data.thing = 'Bananas'
console.log(data)
Your turn:
The file handling/reading/writing code so far has been specific to Bun, which runs on the CLI/server but not in a browser. In a browser, you can use localStorage to save and read JSON using its simple key-value store of strings. This stores the data in the browser per domain/website rather than your normal file system, but persists across refreshes just like a regular file would.
<!DOCTYPE html>
<title>localStorage</title>
<script>
let stuff = {
items: [3, 'a', 'b'],
people: { bob: 'yellow', ringo: 'green' }
}
localStorage.setItem('someKey', JSON.stringify(stuff))
</script>
... instead of 'someKey' you should choose a sensible name.
And here's the opposite, to read that 'file' back again, for example after refreshing the web page:
let data = JSON.parse(localStorage.getItem('someKey'))
console.log(data)
... make sure you use the same key as you did when setting (here I used 'someKey' for both). Try refreshing the page or closing and reopening your browser, and you'll see the data persists.
Your turn:
localStorage key of your choicelocalStorage)localStorage keys directly from the developer tools console/REPL
JSON.parse()/JSON.stringify()localStorage.clear() clears everything storedlocalStorage.removeItem('blah') removes a single itemSometimes we'll create JSON output/files on a server (often as the output of an SQL database) and want to read those in the browser. To do that, we can use fetch(). To run the code in this section, create a folder and in there you'll need 2 files: index.html which is what gets sent to the client/browser by the server; and server.js which gets run by Bun to create a web server:
Server (server.js):
let data = { students: ['alice', 'bob', 'carol', 'dave'] }
let server = Bun.serve({
routes: {
'/': new Response(Bun.file('./index.html')),
'/data.json': Response.json(data),
'/*': new Response('404')
}
})
console.log(`Listening on ${server.url}`)
Client (index.html):
<!DOCTYPE html>
<title>JSON fetching</title>
<h1>Names fetched from JSON on server</h1>
<ul id="studentList"></ul>
<script>
;(async () => {
let data = await (await fetch('/data.json')).json()
studentList.innerHTML = data.students.map(name => {
return `<li>${name}</li>`
}).join('\n')
})()
</script>
Now run that server with bun --hot server.js and it should tell you Listening on http://localhost:3000. Go to http://localhost:3000 in your browser and you'll be sent the contents of index.html which in turn will do a request to /data.json which has the data needed to populate a list with names from the server. Stop the server with ctrl+c - you'll need to restart it any time you change server.js.
Your turn: (remember to restart the server whenever you change server.js, or use bun --hot server.js to auto-reload)
/cars send back the file cars.html/cars.json send back a file with the same namecars.html so it fetches /cars.json and does something with itWe can use JSON as a simple single-user persistent database in either the client/browser (via localStorage) or on the server/CLI (via Bun.write and Bun.file). Here I'll show you how to do it either on the client or server.
As a database in the client/browser (eg saved as db.html and opened in a browser):
<!DOCTYPE html>
<title>Client-side database</title>
<script>
let dbKey = 'database'
// load the db when we load the page
let db = JSON.parse(localStorage.getItem(dbKey)||"{}")
console.log('Before', db)
// ... do things with the db here, eg as a result of user actions
// ... ***ADD YOUR OWN CODE HERE***, eg to do thing with user input/buttons
db.something = Math.random()
console.log('After', db)
// just before we leave the page (eg on refresh), store the current db
onbeforeunload = () => {
localStorage.setItem(dbKey, JSON.stringify(db))
}
</script>
... easiest is to use the file as-is above, then interact with db via the developer tools console, eg typing things like db.mrGordonRocks = true then reload the page and seeing that I still rock even after reloading!
Or as a database on the server/CLI (eg saved as db.js and run with bun db.js):
let fileName = 'database.json'
// load the db on startup (checking if it exists first)
let file = Bun.file(fileName)
let db = await file.exists() ? await file.json() : {}
console.log('Before', db)
// ... do things with the db here, eg as a result of user actions
// ... ***ADD YOUR OWN CODE HERE***, eg to do thing with user input/buttons
db.something = Math.random()
console.log('After', db)
// store the db before exit
await Bun.write(fileName, JSON.stringify(db))
Your turn: