React Native with WatermelonDB: A Lightweight and Reactive Database for Scalable Apps

sachingaggar

Sachin Gaggar

Posted on September 27, 2024

React Native with WatermelonDB: A Lightweight and Reactive Database for Scalable Apps

When it comes to building scalable React Native applications, WatermelonDB is an excellent lightweight database solution. Adding only about 2MB to your app's size, it offers excellent performance, real-time reactivity, and allows you to manage complex relationships between data models efficiently. With WatermelonDB, you can easily handle operations like creating, reading, updating, and deleting records, and manage relationships using decorators like @children and @relation. It also provides you with sync functionality to remote databse.

In this blog, we'll cover how to set up WatermelonDB, define more complex models with relationships, and perform CRUD operations, including setting children and related records.

Setting Up WatermelonDB in React Native

1. Installation

Start by installing WatermelonDB in your React Native project:
npm install @nozbe/watermelondb
npm install @nozbe/watermelondb/native --save
npx pod-install

2. Define the Schema

Define the schema for your database. We’ll add some extra fields like age for users and content for posts, plus we’ll establish a relationship where users have multiple posts.

// schema.js
import { tableSchema } from '@nozbe/watermelondb/Schema';

export const mySchema = {
  version: 1,
  tables: [
    tableSchema({
      name: 'users',
      columns: [
        { name: 'name', type: 'string' },
        { name: 'email', type: 'string' },  // New field for email
        { name: 'age', type: 'number' },    // New field for age
      ],
    }),
    tableSchema({
      name: 'posts',
      columns: [
        { name: 'user_id', type: 'string', isIndexed: true },  // Foreign key to users
        { name: 'title', type: 'string' },
        { name: 'content', type: 'string' },  // New field for content
        { name: 'created_at', type: 'number' },  // New timestamp field
      ],
    }),
  ],
};

Enter fullscreen mode Exit fullscreen mode

3. Define Models with Relationships

WatermelonDB uses decorators like @field, @children, and @relation to manage fields and relationships between models. Let’s define a User model that has many Post records, and a Post model that belongs to a User.

// User.js
import { Model } from '@nozbe/watermelondb';
import { text, field, date, children } from '@nozbe/watermelondb/decorators';

export default class User extends Model {
  static table = 'users';

  @text('name') name;
  @text('email') email;      // New field for email
  @field('age') age;          // New field for age

  @children('posts') posts;   // One-to-many relationship with posts
}

Enter fullscreen mode Exit fullscreen mode
// Post.js
import { Model } from '@nozbe/watermelondb';
import { text, date, relation } from '@nozbe/watermelondb/decorators';

export default class Post extends Model {
  static table = 'posts';

  @text('title') title;
  @text('content') content;        // New field for content
  @date('created_at') createdAt;   // New timestamp field

  @relation('users', 'user_id') user;  // Relation to the users table
}

Enter fullscreen mode Exit fullscreen mode

4. Set Up the Database

You now need to set up the database with the schema and models you defined:

// database.js
import { Database } from '@nozbe/watermelondb';
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
import { mySchema } from './schema';
import User from './User';
import Post from './Post';

const adapter = new SQLiteAdapter({
  schema: mySchema,
});

export const database = new Database({
  adapter,
  modelClasses: [User, Post],
  actionsEnabled: true,
});

Enter fullscreen mode Exit fullscreen mode

CRUD Operations with WatermelonDB

Let’s walk through performing CRUD operations with WatermelonDB, including handling relations between users and posts.

1. Create a User and a Post

You can create new users and posts by using the create method inside a database.write block. Remember always write crud operation inside write block. Now here you have freedom either, we can create this method directly inside models or write a separate file.:

// Create a User
const addUser = async (name, email, age) => {
  await database.write(async () => {
    await database.collections.get('users').create(user => {
      user.name = name;
      user.email = email;  // Setting email field
      user.age = age;      // Setting age field
    });
  });
};

// Create a Post for a User
const addPost = async (user, title, content) => {
  await database.write(async () => {
    await database.collections.get('posts').create(post => {
  /**
  * Setting relation to User. This is very very important.
  * Without this set you can't take advantage of flexibilty provided by children decorator
  **/
      post.user.set(user);  
      post.title = title;
      post.content = content;  // Setting content field
      post.createdAt = Date.now();  // Setting timestamp
    });
  });
};

Enter fullscreen mode Exit fullscreen mode

2. Read Records

Fetching records is straightforward, and you can query related data like posts belonging to a user.

// Fetch all users
const fetchUsers = async () => {
  const users = await database.collections.get('users').query().fetch();
  return users;
};

// Fetch posts for a specific user
const fetchUserPosts = async (user) => {
  const posts = await user.posts.fetch();  
// Using @children decorator all posts will be collected 
// related to this user
  return posts;
};

// Fetch the user who owns a post
const fetchPostOwner = async (post) => {
  const user = await post.user.fetch();  // Fetches the related user using @relation
  return user;
};

Enter fullscreen mode Exit fullscreen mode

3. Update Records

Updating records is done by fetching a record and modifying it within a database.write block:

// Update a User
const updateUser = async (user, newName, newAge) => {
  await database.write(async () => {
    await user.update(u => {
      u.name = newName;
      u.age = newAge;
    });
  });
};

// Update a Post
const updatePost = async (post, newTitle, newContent) => {
  await database.write(async () => {
    await post.update(p => {
      p.title = newTitle;
      p.content = newContent;
    });
  });
};

Enter fullscreen mode Exit fullscreen mode

4. Delete Records

WatermelonDB allows both soft delete (marks as deleted) and hard delete (permanently removes the record):

// Soft delete a User
const softDeleteUser = async (user) => {
  await database.write(async () => {
    await user.markAsDeleted();
  });
};

// Hard delete a User
const hardDeleteUser = async (user) => {
  await database.write(async () => {
    await user.destroyPermanently();
  });
};

Enter fullscreen mode Exit fullscreen mode

Conclusion

WatermelonDB is a lightweight, high-performance solution for React Native apps, adding only 2MB to your app size while providing efficient data handling for large datasets. By leveraging decorators like @children and @relation, you can easily define and manage relationships between records. The asynchronous CRUD operations ensure smooth UI performance, making it an ideal database choice for both small and large-scale applications.

Whether you're handling complex relationships or just starting with simple data, WatermelonDB’s combination of reactivity, performance, and ease of use makes it a great option for React Native developers.

Feel free to drop a comment if you have any questions. Also, take a look at withObservables, a Higher-Order Component (HOC) that automatically re-renders your UI whenever there are changes in the database values.

💖 💪 🙅 🚩
sachingaggar
Sachin Gaggar

Posted on September 27, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related