Categories
JavaScript APIs

IndexedDB Manipulation with Dexie — Transactions and Versioning

IndexedDB is a way to store data in the browser.

It lets us store larger amounts of data than local storage in an asynchronous way.

Dexie makes working with IndexedDB easier.

In this article, we’ll take a look at how to start working with IndexedDB with Dexie.

Transactions

When we do more than one operation to our database in sequence, we would normally use a transaction.

Transactions are only completed when the operation succeeds.

This is to ensure that we catch errors and don’t have any partially completed operations done to ur database.

Any errors will cause the operation to roll back.

We can use it to all the write operations synchronously without the need to wait for it to finish before starting the next one.

For example, we can write:

(async () => {
  const db = new Dexie("FriendsAndPetsDB");
  await db.version(1).stores({
    friends: "++id,name,isCloseFriend",
    pets: "++id,name,kind"
  });
  await db.open();
  db.transaction("rw", db.friends, db.pets, async () => {
    await db.friends.add({
      name: "james",
      isCloseFriend: true
    });
    await db.pets.add({
      name: "mary",
      kind: "dog",
      fur: "long"
    });
  })
})()

to add entries to our friends and pets store simultaneously.

We call db.transaction to start a transaction.

The first argument is the permissions we grant for the transaction.

r is for read, and w is for write.

db.friends and db.pets are the data stores.

The callback has the code that we want to run in the transaction.

Database Versioning

Database versioning is essential when working with IndexedDB.

For example, we can write:

(async () => {
  const db = new Dexie("FriendsDB");
  db.version(1).stores({
    friends: "++id,name"
  });
  db.friends.put({
    name: "james",
    phone: "123456",
    email: "james@edxample.com",
    age: 20
  });
})()

to create version 1 of our FriendsDB with:

const db = new Dexie("FriendsDB");
db.version(1).stores({
  friends: "++id,name"
});

We have the id primary key column, which autoincrements as indicated by the ++ operator.

id is an index on the property name.

We may store other properties as we wish.

If we want to add another column to the index, then we increment the version number by writing:

await db.version(2).stores({friends: "++id,name,age"});

We add the age column to the index and increment the database version to 2.

If we need to change the data architecture, then we need to increment the database version and call the upgrade method.

For example, we write:

(async () => {
  const db = new Dexie("FriendsDB");
  db.version(1).stores({
    friends: "++id,name"
  });
  db.version(2).stores({
    friends: "++id,name,age"
  });
  db.version(3).stores({
    friends: "++id,age,firstName,lastName"
  }).upgrade(tx => {
    return tx.table("friends").toCollection().modify(friend => {
      const [firstName, lastName] = friend.name.split(' ');
      friend.firstName = firstName;
      friend.lastName = lastName;
      delete friend.name;
    });
  });
})()

to upgrade the database version with the version method.

Then to update the database structure, we call the modify method with a callback to update existing data to the new architecture.

Now all the existing data would upgrade for existing users.

Conclusion

Dexie supports transactions so that we only commit complete operations.

Also, we can update the indexes and schema by upgrading the database version.

Categories
JavaScript APIs

Getting Started with IndexedDB Manipulation with Dexie

IndexedDB is a way to store data in the browser.

It lets us store larger amounts of data than local storage in an asynchronous way.

Dexie makes working with IndexedDB easier.

In this article, we’ll take a look at how to start working with IndexedDB with Dexie.

Getting started

We can get started by adding Dexie with a script tag:

<script src="https://unpkg.com/dexie@latest/dist/dexie.js"></script>

Then we can use it to create our data.

Then we can use it create a database, and write and read data from it.

To do this, we write:

const db = new Dexie("friend_database");
(async () => {
  try {
    await db.version(1).stores({
      friends: 'name,age'
    });
    await db.friends.put({
      name: "mary",
      age: 28
    })
    const friend = await db.friends.get('mary');
    console.log(friend.age);
  } catch (error) {
    console.log(error);
  }
})()

We create the database with the Dexie constructor.

Then we create a store in the database with:

await db.version(1).stores({
  friends: 'name,age'
});

We add the friends store into the database with the name and age fields indexed.

Indexed columns can be used to search for an entry.

Then we add the data with the put method:

await db.friends.put({
  name: "mary",
  age: 28
})

Then we get an entry with the given indexed column with:

const friend = await db.friends.get('mary');

Then get the value of the age field with:

console.log(friend.age);

Using Dexie as a Module

We can use Dexie withn the dexie module.

To install it, we run:

npm i dexie

Then can write the same code with:

import Dexie from "dexie";
const db = new Dexie("friend_database");

(async () => {
  try {
    await db.version(1).stores({
      friends: "name,age"
    });
    await db.friends.put({
      name: "mary",
      age: 28
    });
    const friend = await db.friends.get("mary");
    console.log(friend.age);
  } catch (error) {
    console.log(error);
  }
})();

The Dexie Class

The Dexie class is both a class and a namespace.

The Dexie instance represents a database connection.

It can also be used as an export area for functions, utilities, and classes.

If it’s used in the browser as a script tag, then only the window.Dexie property is added.

If it’s used as a module, then it’s available as a default export.

The Table Class

The table represents an object-store.

We have direct access to instances of Table for each object store that we’ve denied in our schema.

For example, if we have:

(async () => {
  const db = new Dexie("FriendsAndPetsDB");
  await db.version(1).stores({
    friends: "++id,name,isCloseFriend",
    pets: "++id,name,kind"
  });
  await db.open();
  await db.friends.add({
    name: "james",
    isCloseFriend: true
  });
  await db.pets.add({
    name: "mary",
    kind: "dog",
    fur: "long"
  });
})()

Then we created the friends and pets store.

db.friends and db.pets are the table instances.

And we can manipulate data with them.

db.open opens the database connection.

We called db.friends.add to add data to the db.friends store.

And we called db.pets.add to add data to the db.pets store.

Conclusion

We can manipulate IndexDB data easily with the Dexie library.

Categories
Deno

Deno — OS Signals, File System Events, and Module Metadata

Deno is a new server-side runtime environment for running JavaScript and TypeScript apps.

In this article, we’ll take a look at how to get started with developing apps for Deno.

Handle OS Signals

We can handle signals with the Deno.signal method.

For example, we can write:

index.ts

console.log("Press Ctrl-C");
for await (const _ of Deno.signal(Deno.Signal.SIGINT)) {
  console.log("interrupted!");
  Deno.exit();
}

If we press Ctrl+C, we’ll trigger the sigint signal to interrupt the program.

We watch for the signal with the for-await-of loop.

Deno.Signal.SIGINT is the object for the sigint signal.

We call Deno.exit to exit the program.

We run the program by running:

deno run --unstable index.ts

Also, we can write it as a promise.

For instance, we can write:

index.ts

console.log("Press Ctrl-C to end the program");
await Deno.signal(Deno.Signal.SIGINT);
console.log("interrupted!");
Deno.exit();

to watch for the sigint signal.

Stop Watching Signals

We can stop watching signals by calling the sig.dispose method.

For example, we can write:

index.ts

const sig = Deno.signal(Deno.Signal.SIGINT);
setTimeout(() => {
  sig.dispose();
  console.log("No longer watching SIGINT signal");
}, 5000);

console.log("Watching SIGINT signals");
for await (const _ of sig) {
  console.log("interrupted");
}

In the setTimeout callback. we call the sig.dispose method to stop watching the sigint signal.

The for-await-of loop exits after 5 seconds when sig.dispose is called.

File System Events

We can watch for file system events with the Deno.watchFs method.

For example, we can write:

index.ts

const watcher = Deno.watchFs(".");
for await (const event of watcher) {
  console.log(event);
}

We watch the folder the script is in with Deno.watchFs .

We get the event from the event object.

Then we something like:

index.ts

{ kind: "create", paths: [ "/home/runner/IntelligentWorthwhileMice/./lock.json" ] }
{ kind: "modify", paths: [ "/home/runner/IntelligentWorthwhileMice/./lock.json" ] }
{ kind: "access", paths: [ "/home/runner/IntelligentWorthwhileMice/./lock.json" ] }
{
  kind: "create",
  paths: [ "/home/runner/IntelligentWorthwhileMice/./.3s18gah600nwj.foo.txt~" ]
}
{
  kind: "access",
  paths: [ "/home/runner/IntelligentWorthwhileMice/./.3s18gah600nwj.foo.txt~" ]
}
{
  kind: "modify",
  paths: [ "/home/runner/IntelligentWorthwhileMice/./.3s18gah600nwj.foo.txt~" ]
}
{ kind: "modify", paths: [ "/home/runner/IntelligentWorthwhileMice/./foo.txt" ] }
{
  kind: "modify",
  paths: [
    "/home/runner/IntelligentWorthwhileMice/./.3s18gah600nwj.foo.txt~",
    "/home/runner/IntelligentWorthwhileMice/./foo.txt"
  ]
}

displayed.

Then we can run it with:

deno run --allow-read index.ts

to watch for any file system events in the folder.

Module Metadata

We can get module meta with the import.meta property.

For example, we can write:

console.log(import.meta.url);
console.log(Deno.mainModule);
console.log(import.meta.main);

Then we see something like:

file:///home/runner/IntelligentWorthwhileMice/index.ts
file:///home/runner/IntelligentWorthwhileMice/index.ts
true

from the console output when we run:

deno run --allow-read --unstable index.ts

import.meta.url and Deno.mainModule both get the path of the module.

And import.meta.main returns true if it’s the entry point module.

Conclusion

We can handle OS signals, get module metadata, and watch file system events with Deno.

Categories
Deno

Deno — HTTP, Server, File Server, and Subprocesses

Deno is a new server-side runtime environment for running JavaScript and TypeScript apps.

In this article, we’ll take a look at how to get started with developing apps for Deno.

Simple HTTP Web Server

We can create a simple HTTP server with the server module provided by Deno.

For example, we can write:

import { serve } from "https://deno.land/std@0.75.0/http/server.ts";

const server = serve({ hostname: "0.0.0.0", port: 8080 });
console.log(`HTTP server running on port 8080`);

for await (const request of server) {
  const bodyContent = `Your user-agent is: ${request.headers.get("user-agent") || "Unknown"}`;
  request.respond({ status: 200, body: bodyContent });
}

We import the serve function to create our HTTP server.

The object has the hostname and port to listen to requests from.

Then we create the response body in the for-await-of loop.

We get the header by calling request.headers.get .

Then we call request.response with the HTTP status code and body in the object that we use as the argument of respond .

Then we can run the script with:

deno run --allow-net index.ts

Then when we go to http://localhost:8080, we should see something like:

Your user-agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36

File Server

We can also create a file server easily with Deno.

To do this, we just run:

deno run --allow-net --allow-read https://deno.land/std@0.75.0/http/file_server.ts

Then when we go to http://localhost:4507/, we see the files in the current folder listed.

TCP Echo Server

We can create a TCP echo server easily with the Deno.listen and Deno.copy methods.

For example, we can write:

const listener = Deno.listen({ port: 8080 });
console.log("listening on 0.0.0.0:8080");
for await (const conn of listener) {
  Deno.copy(conn, conn);
}

to listen to port 8080.

Then we loop through the listener array to watch for requests.

Then we call Deno.copy to redirect the connection data inbound.

Creating a Subprocess

We can create a subprocess by using the Deno.run method.

For example, we can run:

const p = Deno.run({
  cmd: ["echo", "hello"],
});
await p.status();

to run echo hello .

Then we can wait for its completion with the p.status method.

We need the --allow-run flag to let us run subprocesses.

We can get results from subprocesses and run with them by using various methods.

For instance, we can write:

const fileNames = Deno.args;

const p = Deno.run({
  cmd: [
    "deno",
    "run",
    "--allow-read",
    "https://deno.land/std@0.75.0/examples/cat.ts",
    ...fileNames,
  ],
  stdout: "piped",
  stderr: "piped",
});

const { code } = await p.status();

if (code === 0) {
  const rawOutput = await p.output();
  await Deno.stdout.write(rawOutput);
} else {
  const rawError = await p.stderrOutput();
  const errorString = new TextDecoder().decode(rawError);
  console.log(errorString);
}

Deno.exit(code);

We call the status method to get the status code.

Then we write the output if the code is 0.

Otherwise, we output the error to the stderr .

And we log the string.

And finally, we call Deno.exit with the exit code of our command.

Conclusion

We can create a simple HTTP server, file server, and run subprocesses easily with Deno.

Categories
Deno

Deno — HTTP Requests and File Manipulation

Deno is a new server-side runtime environment for running JavaScript and TypeScript apps.

In this article, we’ll take a look at how to get started with developing apps for Deno.

Managing Dependencies

We can manage dependencies in our Deno apps.

We can reexport modules with the export statement:

math.ts

export {
  add,
  multiply,
} from "https://x.nest.land/ramda@0.27.0/source/index.js";

index.ts

import { add, multiply } from "./math.ts";

console.log(add(1, 2))
console.log(multiply(1, 2))

We reexport the items in math.ts and import it in index.ts .

Fetch Data

We can fetch data with the Fetch API with Deno.

The Fetch API is implemented with Deno.

For example, we can write:

const res = await fetch("https://yesno.wtf/api");
const json = await res.json()
console.log(json);

Then when we run:

deno run --allow-net index.ts

We get something like:

{
  answer: "yes",
  forced: false,
  image: "https://yesno.wtf/assets/yes/7-653c8ee5d3a6bbafd759142c9c18d76c.gif"
}

logged.

The --allow-net flag enables network communication in our script.

To get HTML, we can write:

const res = await fetch("https://deno.land/");
const html = await res.text()
console.log(html);

Then we download the HTML code from the deno.land URL.

To catch errors, we write:

try {
  await fetch("https://does.not.exist/");
} catch (error) {
  console.log(error.message)
}

Then we log the message from the error.message property.

Read and Write Files

Deno comes with an API for reading and writing files.

They come with both sync and async versions.

The --allow-read and --allow-write permissions are required to gain access to the file system.

For example, we can write:

index.ts

const text = await Deno.readTextFile("./people.json");
console.log(text)

people.json

[
  {
    "id": 1,
    "name": "John",
    "age": 23
  },
  {
    "id": 2,
    "name": "Sandra",
    "age": 51
  },
  {
    "id": 5,
    "name": "Devika",
    "age": 11
  }
]

Then when we run:

deno run --allow-read index.ts

We read the JSON file and log the data.

Deno.readTextFile returns a promise so we can use await .

Writing a Text File

To write a text file, we can use the writeTextFile method.

For example, we can write:

await Deno.writeTextFile("./hello.txt", "Hello World");
console.log('success')

to create hello.txt and use the string in the 2nd argument as the content.

Then when we run:

deno run --allow-write index.ts

We can also use the writeTextFileSync method to write text file synchronously.

For example, we can write:

try {
  Deno.writeTextFileSync("./hello.txt", "Hello World");
  console.log("Written to hello.txt");
} catch (e) {
  console.log(e.message);
}

Read Multiple Files

We can read multiple files by writing:

for (const filename of Deno.args) {
  const file = await Deno.open(filename);
  await Deno.copy(file, Deno.stdout);
  file.close();
}

Then when we run:”

deno run --allow-read index.ts foo.txt bar.txt

We should see the contents of foo.txt and bar.txt printed.

We call Deno.open to open each file.

Deno.args has the command line arguments, which is ['foo.txt', 'bar.txt'] .

And then we call Deno.copy to copy the content to stdout .

And then we close the filehandle with file.close .

Conclusion

We can make HTTP requests with the fetch API and read and write files with Deno.