Get started with Rowy in minutes
Database security is not a given: a third of all businesses will experience a data breach in the next two years. And with a data breach taking 191 days on average to be discovered, it is no surprise that security risks can potentially cost you millions of dollars! This is why database testing is key to fix potential leaks early before they blow up your entire infrastructure.
Our job at Rowy is to make working with databases easy, so you will find in the following article how to integrate the best practices we use to protect our customers’ data from your front-end interface. Don’t wait too long to add them to your roadmap!
Database testing is a set of practices making sure your database behaves as it is expected to:
To make it easier to build a database, we are going to use Rowy. Set up a database and create a table called Blog with a single column named title. This Rowy table is connected to a live Firestore database collection and ready to use.
We can now jump into a Javascript repository in your local machine and get started.
First, you need to make sure your database access is protected. You don’t want anyone to be able to access private data they aren’t supposed to look at. Authentication and authorization solve this.
Authentication verifies the identity of a user. When you log in your favorite social media platform, you need to provide your email and a password: that’s authentication. By default, only you can access the database as an administrator. Let’s make sure of that first. In Javascript, you can use the mocha library to write simple unit tests in any repository:
import assert from 'assert'
import { initializeApp } from "firebase/app"
describe('Database', () => {
describe('#auth()', () => {
let firebaseConfig = {
# ...
apiKey: "FIREBASE_API_KEY",
# ...
}
it('connecting to the database requires the right API key', () => {
assert.doesNotThrow(() => {
initializeApp(firebaseConfig)
});
})
it('the wrong API key triggers an error', () => {
firebaseConfig.apiKey = "wrong API key"
assert.throws(() => {
initializeApp(firebaseConfig)
});
})
})
})
Just run npm run mocha from the command line and you'll obtain your test results:
$ npm run mocha
Database
#auth()
✔ connecting to the database requires the right API key
✔ the wrong API key triggers an error
2 passing (9ms)
Done in 0.28s.
A database isn’t useful if you can’t have non-admin users interact with it. You can use an auth provider like Firebase Authentication or Auth0 to remedy that.
At this point, you can authenticate any type of users against your database. But you also need to take care of authorization―making sure user accounts have the correct parameters to access and manage their own data. In Firebase, you can set up security rules to do just that.
For example, we can tell Firebase to prevent access to any document unless the user is authenticated. This is useful for an app where you need to login first before being able to use it. In the Firebase console:
service cloud.firestore {
match /databases/{database]/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
And the test:
import assert from 'assert'
import { initializeApp } from "firebase/app"
import { getFirestore, collection, query, getDocs } from "firebase/firestore"
describe('Database', () => {
describe('#auth()', () => {
it("can't access collection because of authorization rule", async () => {
let firebaseConfig = {
# ...
apiKey: "FIREBASE_API_KEY",
# ...
}
const app = initializeApp(firebaseConfig)
const db = getFirestore(app)
const blogRef = collection(db, "Blog")
const blogs = query(blogRef)
await assert.rejects((async () => {
const res = await getDocs(blogs)
})())
})
})
})
The second thing you need to test is if you can actually do what you want to do with the database. If the database transactions do not work as expected, your feature is broken. Functional testing makes sure you can provide the service your users expect.
You want to be at least able to create, read, update, and delete (CRUD) database documents, preferably using a randomized dataset while respecting specified constraints. Let’s try to create a new blog post:
await setDoc(doc(db, "Blog", "a random id"), {
title: "a new blog post"
});
We can always check in Rowy if we are tired of testing the result with code:
Functional testing also validates the data―making sure you have the right data types, or **** testing for SQL injection patterns, for example. ****Whatever kind of database you’re using, you’ll need your data to follow a certain structure: structural testing makes sure the documents follow the design described by your business requirements. You want to test things like collection names, properties, and indexes, to make sure they match your schema specification.
Say you have a view counter for each article, you should test it’s a number before updating its value in the database:
assert.equal(isNaN(blog_post.view_count), false)
Since a database is a software like any other, you need a server to host it and make it available online for other services to use. And if the server goes down, so does your database! You can avoid that by following 3 best practices:
Running separate coding environments for different stages of your product lifecycle is key to efficiently catching and fixing bugs. You’ll need at least a development environment to interact with new features on a local machine and a production environment where people buy your services. With Firebase, you can set your Security Rules for a given Firestore instance to use Test mode:
// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this rule set in production; it allows
// anyone to overwrite your entire database.
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
With a managed database service like Rowy built on top of Firebase, you’ll set up one project per environment. It’ll only take a click to switch between the two:
And integrating changes from one environment to another is as simple as copy/pasting:
Once your database is available in production, you want to make sure it is available and performing well at all times. That’s where monitoring tools come in. A monitoring service like Firebase’s usage dashboard will check your database server activity and report in real-time:
A single database can only handle a limited number of concurrent connections, so you’ll need to test how much load your infrastructure can handle to avoid down-times. Load testing measures the resistance of your database against crashes and traffic spikes, allowing you to scale your database infrastructure ahead of time.
Let’s say you have a web page that query rows from a database. You can use a Javascript library like the k6 package to ping this live webpage before remaining idle for a second:
//load-test.js
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.get('https://my-cool-website.com');
sleep(1);
}
And then we use k6’s CLI to simulate 100 users connecting to this webpage in the span of 10 seconds:
k6 run --vus 100 --duration 10s load-test.js
If you use a cloud provider like Firebase, make sure to use a development environment, or you might cause a denial of service attack without noticing. Also, don’t play too much with it unless you want your Firebase bill to explode!
Speaking of DoS attacks, they are the most common type of cyber attacks nowadays―78,558 DDoS attacks in Q2 2022 alone!―so make sure to have a way to monitor whether your database can handle the scale or not. In production, you might want to look into installing a load balancer (a reverse proxy that distributes traffic among different databases―complex to configure) and having your database cached data served over a Content Deliverivery Network (like Cloudflare’s).
One of the main advantages of database providers like Rowy / Firebase is the ability to scale to infinity without any additional work, which is welcome if you aren’t a database expert. Thanks to the Firebase Web Performance Monitoring dashboard, you can monitor what happens with your database performance in real-time:
You need to account for the worst case scenario where your database would be corrupted by a black swan event, resulting in a loss of data. Having a backup system ready is mandatory to mitigate data loss, but you’ll also need to monitor the backup system in case it breaks down. 3 steps to implement a robust database backup process:
In Firestore, you can export / import data any time from the Google Cloud Platform Console:
Combined with a scheduled data export using a Cloud function and a Cloud Scheduler job, you can automate your database backup completely:
Lastly, tests aren’t any good if you don’t actually run them on each breaking change you make to your database: integrate your tests in your CI/CD pipeline! Chances are you are using devops tools like Github Actions to deliver your code to a production environment. Don’t hesitate to copy/paste all the tests above and run them on each commit!
Let’s say we want to run our tests on each git push―this is the workflow you’ll need to run your test suite:
name: Database Testing a React app
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run mocha
Rowy offers all the benefits of a feature-complete database management system, with a low learning curve and built-in best practices for database testing! It’s the perfect way to minimize your costs of switching to a secure database.
The best part? We’ve got plenty of templates to get you up and running in 2 minutes! So don’t hesitate and try Rowy for free.