@jgombero/database-package v1.0.1
database-package-nicejob
A mini ORM to read and write data to and from a GCP firebase database.
Purpose
BEWARE: This library was published for learning purposes. It is not intended for use in production-grade software.
This project was created and published by me as part of a take-home assignment
Usage
Install it:
npm install @jgombero/database-package
Require it:
const db = require('@jgombero/database-package');
Call it:
const document = await db.readOne({ collection, id });
Documentation
database-package
This NPM package provides a simple interface for saving and retrieving data to and from a database. It will be accompanied by a simple companion application that utilizes the package. The application will be containerized & deployed to GCP – the Google Cloud Platform.
Overview
There's nothing more core to a web application than reading from and writing to its database. This package provides methods to enable just that: reading from a database, and writing to a database.
NPM package
In particular, this package reads from and writes to a GCP Firestore database.
But it also does a bit more. When writing data, the package saves that data to an in-memory cache. When retrieving data, it first checks its in-memory cache for matching data. If a match for the query is found, and if the match is sufficiently fresh, it will return that data without querying Firestore.
The size of the cache is limited. The package will have to make sure that the size of the cache – in bytes – never eclipses its allocation.
Use the package reference below to guide the development of your package.
Companion server application
Write a companion server application using popular NPM server package express to showcase the packages's basic functionality.
Have the application handle the following requests, responding in JSON:
GET /:collection?limit=LIMIT– ReturnsLIMIT(default: 10) documents of the collection:collectionGET /:collection/:id– Returns a single document of collection:collectionand ID:idPOST /:collection– Creates a document in the:collectioncollection using the submitted data, returns that document JSON-formattedPOST /:collection/:id– Updates a document of collection:collectionand ID:idusing the submitted data, returns that document JSON-formatted
The application should also handle a simple health check request.
Application container
Write a simple Dockerfile to containerize the application, and a script to build an image from the Dockerfile.
Deployment
The companion application is to be deployed onto the Google Cloud Platform. Use your Dockerfile to create a container, push that container to GCP, and use it to create a Compute Engine Instance Template. Use that template to deploy an auto-scaling Compute Engine Managed Instance Group. For the purposes of this challenge the auto-scaling settings need not permit more than 1 concurrent instance, but configure the group such that changing the maximum concurrency is trivial.
Submission & questions
Please complete this challenge and respond to the code test email in the next 5 days. Please ensure that the email's subject line is "NiceJob Database Package Challenge".
We're looking for two things in your email: 1) A .zip attachment containing your project directory, with the following:
- Database package
- Companion server application
- Dockerfile 2) A 10-minute-or-less Loom (or equivalent) video walkthrough of your code. In the video, please demonstrate usage of your project, the commands you've written to manage it (deployment, etc.), and discuss the design decisions you've made within your code.
If you have any questions along the way, please respond to the code test email.
Relevant GCP documentation
Compute Engine Instance Templates
Compute Engine Managed Instance Group
Firestore
Package reference
The package must export a class that can both meet the criteria provided in the NPM package section above, and accommodate the provided constructor and methods below.
Constructor
import Database from 'database-module';
const db = new Database({
project_id,
cache_max_age,
cache_allocated_memory,
})Constructor options
| Property | Type | Description | Default value |
|---|---|---|---|
project_id | String | Google Cloud Platform project ID | |
cache_max_age | Number (seconds) | Cached data age threshold | 3600 |
cache_allocated_memory | Number (MB) | Maximum in-memory cache size, in megabytes | 64 |
- When retrieving data, if the cache-matched data was added more than
cache_max_ageseconds ago, we do not return the cached data
Methods
write\({ collection, id }, document)
Writes a document to the database, and to the in-memory cache.
await db.write<DataType>({ collection, id }, document);Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
collection | string whose value is enforced by DataType | ✓ | Firestore collection |
id | string | ✓ | Firestore document ID |
document | object whose shape is enforced by DataType | ✓ | Firestore document ID |
Returns
This method does not return anything.
Typescript (optional)
- The generic
DataTypeshould confer both the structure ofdocumentand the value ofcollection- e.g. a type
UserTypedemands thatdocumentinclude afirst_name,last_name, andemail; while also demanding that thecollectionvalue is"Users"
- e.g. a type
- Enforce that all property names of
documentare strings, and that all property value types are either strings, numbers, booleans, or arrays of strings- Property values may also be nested objects that meet these same criteria
Errors
This method should throw if:
- Either
collection,id, ordocumentare not provided, or are falsy – all are required
Example usage
/**
* Save a document
*/
await db.write<UserType>({
collection: "Users", // Enforced by UserType
id: "23"
}, { // Enforced by UserType
first_name: "Michael",
last_name: "Angelo",
email: "mangelo@sistine.org",
})readOne\({ collection, id })
Retrieves a single document from the database, or, if applicable, from the in-memory cache.
const document = await db.readOne<DataType>({ collection, id });Parameters
| Parameter | Type | Required | Description | |
|---|---|---|---|---|
collection | string whose value is enforced by DataType | ✓ | Firestore collection | |
id | string | ✓ | Firestore document ID |
Returns
This method returns a single document, an object, whose type is to be inferred by the DataType generic. If the document does not exist, the method throws an error.
Typescript (optional)
- The generic
DataTypeshould confer both the structure of the response document and the value ofcollection
Errors
This method should throw if:
- Either
collectionoridare not provided, or are falsy – both are required - If the document does not exist
Example usage
/**
* Retrieve a document from the Users collection
*/
const user = await db.readOne<UserType>({
collection: "Users",
id: "23"
});readMany\({ collection }, filters?)
Retrieves a set of documents from the database, or, if applicable, from the in-memory cache.
const documents = await db.readMany<DataType>({ collection }, filters?);Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
collection | string whose value is enforced by DataType | ✓ | Firestore collection |
filters | object whose shape is enforced by DataType | Query filters |
Returns
This method returns an array of documents, whose type is to be inferred by the DataType generic.
Typescript (optional)
- The generic
DataTypeshould confer both the structure of the documents in the response array and the value ofcollection DataTypewill also restrictfilters– the properties & property values infiltersmust match theDataTypedocument shape- The inclusion of document properties in
filtersis optional; alternatively,filtersdoes not need to be passed in at all
- The inclusion of document properties in
Errors
This method should throw if:
collectionis not provided, or is falsy
Example usage
/**
* Filter Users by first name
*/
const users = await db.readOne<UserType>({
collection: "Users",
}, {
first_name: "Michael"
});