Building a RESTful API with Supabase, Node.js, and Express.js: A Comprehensive Guide

In the ever-evolving landscape of web development, the demand for robust backend solutions has never been higher. Supabase, a dynamic open-source alternative, emerges as a powerful tool for developers seeking simplicity without compromising functionality. In the comprehensive guide, we'll walk through the process of setting up, running and utilizing Supabase with Node.js and Express.js environment to perform CRUD operations.

Setting the Foundation: Node.js and Express.js

Before diving into Supabase, lets establish the groundwork with Node.js and Express.js.

To get started ,we first initialize our Node.js project and install dependencies:

pnpm init
pnpm i express dotenv @supabase/supabase-js

The express package allows us to build the API framework. dotenv will be used to hide sensitive credentials in environment variables. And @supabase/supabase-js is the Supabase client to connect to the database.

The following is the foundational structure that forms the basis of our integration with Supabase:

// Provided Express.js setup code
const express = require('express');
const app = express();

Integrating Supabase: Connecting the Dots

The magic begins when we integrate Supabase into our Node.js and Express.js application. The following code showcases the seamless connection between your application and Supabase using the @supabase/supabase-js library.

// Supabase integration code
const supabaseUrl = process.env.SUPABASE_URL;
const supabaseKey = process.env.SUPABASE_KEY;

const supabase = createClient(supabaseUrl, supabaseKey);

RESTful Endpoints: Unleashing Supabase's Power

Our guide extends beyond basic connectivity to Supabase by implementing RESTful endpoints. From fetching all users to adding, updating, and deleting individual records, these routes leverage Supabase's capabilities while maintaining a clean and efficient code structure.

Create Users:

app.post('/api', async(req, res) => {
    try{
      const { name } = req.body
      const{ data : users, error } = await supabase
            .from('users')
            .insert(req.body)
      if (error) throw error
      res.status(201).json(users)
    } catch (error) {
        console.error(error)
        res.status(500).json({error: "An error occured"})
    }
})

app.post('/api/:params', async(req, res) => {
  try{
      const name = req.params.params
      const{ data : users, error } = await supabase
          .from('users')
          .insert({name})
      if (error) throw error
      res.status(201).json(users)
  } catch (error) {
      console.error(error)
      res.status(500).json({error: "An error occured"})
  }
})

Read Users:

app.get('/api', async(req, res) => {
    try{
      const { name } = req.body
        const{ data : users, error } = await supabase
            .from('users')
            .select()
        if (error) throw error
        res.json(users)
    } catch(error) {
        console.error(error)
        res.status(500).json({error: "An error occurred"})
    }
})


app.get('/api/:params', async (req, res) => {
    try {
      const param = req.params.params
      if (Number.isInteger(parseInt(param))) {
        const userId = parseInt(param)
        const { data: users, error } = await supabase
        .from('users')
        .select('*')
        .eq('id', userId)
        .single();

        if (error) {
          throw new Error(error.message);
        }

        if (users) {
          res.status(200).json(users);
        } else {
          res.status(404).json({ error: 'User not found' });
        }
      } else {
        const paramName = param
        const { data: users, error } = await supabase
        .from('users')
        .select('*')
        .eq('name', paramName)
        .single();

        if (error) {
          throw new Error(error.message);
        }

        if (users) {
          res.status(200).json(users);
        } else {
          res.status(404).json({ error: 'User not found' });
        }
      }
    } catch (error) {
      console.error(error);
      res.status(500).json({ error: 'An error occurred' });
    }
});

Update Users:

app.put('/api/:identifier', async (req, res) => {
  try {
    const identifier = req.params.identifier
    const { name } = req.body
    if (!validateName(name)) {
      return res.status(400).json({ error : "Invalid name format"})
    }

    const isNumber = !isNaN(identifier)
    const filterKey = isNumber ? 'id' : 'name'
    if (filterKey === 'name'){
      const { data: user, error } = await supabase
      .from('users')
      .update({ name })
      .eq(filterKey, identifier);
      if (error) throw error;
      res.json(user);
    } else {
      const { data: user, error } = await supabase
        .from('users')
        .update({ name })
        .eq(filterKey, identifier);
      if (error) throw error;
      res.json(user);
    }     
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'An error occurred' });
  }
})

Delete User:

app.delete('/api/:identifier', async (req, res) => {
  try {
    const identifier = req.params.identifier;

    if (isNaN(identifier)) {
      // Identifier is a name
      const filterKey = 'name';

      const { error } = await supabase
        .from('users')
        .delete()
        .eq(filterKey, identifier);

      if (error) {
        throw error;
      }
    } else {
      // Identifier is an ID
      const filterKey = 'id';

      const { error } = await supabase
        .from('users')
        .delete()
        .eq(filterKey, identifier);

      if (error) {
        throw error;
      }
    }

    res.sendStatus(204);
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: 'An error occurred' });
  }
})

Starting your Server

Finally start the server on a port:

const port = process.env.PORT || 3001
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
})

Conclusion: Empowering Your Backend with Supabase

Congratulations! You've successfully set up a robust backend using Supabase, Node.js, and Express.js. This guide has laid the foundation for a scalable and efficient RESTful API, allowing you to harness the full power of Supabase in your web development projects.

Whether you're a seasoned developer or just getting started, integrating Supabase into your tech stack provides a potent solution for handling data with ease. As you explore further, consider expanding your API, adding authentication, or incorporating additional Supabase features to elevate your web development experience.