<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: react-admin</title>
    <description>The latest articles on Forem by react-admin (@reactadmin).</description>
    <link>https://forem.com/reactadmin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1037851%2F23731c55-0bb4-4f94-a0c3-64a23cf8491d.jpeg</url>
      <title>Forem: react-admin</title>
      <link>https://forem.com/reactadmin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/reactadmin"/>
    <language>en</language>
    <item>
      <title>How to build a CMS with Supabase and React-admin</title>
      <dc:creator>react-admin</dc:creator>
      <pubDate>Mon, 03 Feb 2025 13:21:03 +0000</pubDate>
      <link>https://forem.com/react-admin/how-to-build-a-cms-with-supabase-and-react-admin-57bp</link>
      <guid>https://forem.com/react-admin/how-to-build-a-cms-with-supabase-and-react-admin-57bp</guid>
      <description>&lt;p&gt;In this post, I will explain how to build a CMS proof of concept using &lt;a href="https://marmelab.com/react-admin/" rel="noopener noreferrer"&gt;react-admin&lt;/a&gt; and &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;. You can check out the result in the video below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d9af9pw6a9xfb0b20mj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d9af9pw6a9xfb0b20mj.gif" alt="Image description" width="676" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've been working with &lt;a href="https://marmelab.com/react-admin/" rel="noopener noreferrer"&gt;react-admin&lt;/a&gt; on various projects, some of which required basic CMS features in addition to the core application. We've used headless CMS like Strapi, Directus, or Prismic for these features. However, react-admin is so powerful that it can be used to build the CMS part, too. That's why I worked on a CMS proof-of-concept using react-admin for the admin UI and Supabase (which provides a REST API) for the backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Defining The Data Model&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A CMS requires a data model that is both flexible and dynamic, allowing the addition of new entities and new fields to existing entities. The following diagram represents the data model I came up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqc2b6g2h1vsdi9v77sko.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqc2b6g2h1vsdi9v77sko.jpg" alt="Image description" width="720" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Entities like &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;pages&lt;/code&gt; are stored in an &lt;code&gt;entities&lt;/code&gt; table, while fields such as &lt;code&gt;title&lt;/code&gt; or &lt;code&gt;content&lt;/code&gt; are stored in a fields table. Since an entity can have multiple fields and fields can be shared by multiple entities, it is a many-to-many relationship materialized by the &lt;code&gt;entities_fields&lt;/code&gt; table. A separate &lt;code&gt;field_types&lt;/code&gt; table is needed to store the different PostgreSQL types of fields (text, integer, boolean, etc.).&lt;/p&gt;

&lt;p&gt;If we follow this structure for &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;pages&lt;/code&gt; entities, it results in the following data being stored in the configuration tables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn75t7nv9uzuiub5i6wbq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn75t7nv9uzuiub5i6wbq.jpeg" alt="Image description" width="315" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using SQL Triggers For Dynamic Tables&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The data model described above is the configuration model. I'll need a second model to store data for posts and pages. For instance, when a 'posts' record is added to the &lt;code&gt;entities&lt;/code&gt; table, I want to create a &lt;code&gt;posts&lt;/code&gt; table. The actual posts will be stored in this &lt;code&gt;posts&lt;/code&gt; table. Similarly, when a new post field is added to the &lt;code&gt;entities_fields&lt;/code&gt; table, I want to add a column to the posts table.&lt;/p&gt;

&lt;p&gt;So I need to implement logic to dynamically create, update, or delete entities table based on the data in the &lt;code&gt;entities&lt;/code&gt;, &lt;code&gt;fields&lt;/code&gt;, and &lt;code&gt;field_types&lt;/code&gt; tables. I’ll use SQL triggers to achieve this. They can be configured directly within the Supabase Studio interface.&lt;/p&gt;

&lt;p&gt;The first trigger handles entity tables. It automatically creates, updates, and deletes the table associated with an entity whenever a row is inserted, updated, or removed from the &lt;code&gt;entities&lt;/code&gt; table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F731bd3428n4spocha8yj.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F731bd3428n4spocha8yj.jpeg" alt="Image description" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the triggers to manage the tables in place, I now need another trigger to handle the columns of these tables. This trigger will execute after each insert or delete operation on the &lt;code&gt;entities_fields&lt;/code&gt; join table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe43fbo6nq2kmiyu3hy41.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe43fbo6nq2kmiyu3hy41.jpeg" alt="Image description" width="800" height="698"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the example &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; configuration defined earlier, these triggers will create the following entity tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;posts&lt;/code&gt;: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;draft&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pages&lt;/code&gt;: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;content&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend will only need to query the &lt;code&gt;posts&lt;/code&gt; and &lt;code&gt;page&lt;/code&gt; tables, without having to worry about the underlying structure. This approach offers great flexibility for the configuration and good performance for the data storage.&lt;/p&gt;

&lt;p&gt;The SQL triggers do not account for SQL injection vulnerabilities and could likely be improved in terms of security. I will not cover these aspects in this post. Additionally, other triggers may be needed to handle cases such as renaming a column when a field name is updated, but these are not included in this proof of concept.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using React-Admin Components For CMS Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After bootstrapping a basic React-Admin app with the &lt;a href="https://github.com/marmelab/ra-supabase" rel="noopener noreferrer"&gt;ra-supabase&lt;/a&gt; package, I need to create the &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;show&lt;/code&gt;, &lt;code&gt;create&lt;/code&gt;, and &lt;code&gt;edit&lt;/code&gt; pages for the configuration tables: &lt;code&gt;entities&lt;/code&gt;, &lt;code&gt;fields&lt;/code&gt;, and &lt;code&gt;field_types&lt;/code&gt;. For example, in the &lt;code&gt;entities&lt;/code&gt; form, I use the following code to manage the fields:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn0tp8ug6ffwr9eeeoro.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn0tp8ug6ffwr9eeeoro.jpeg" alt="Image description" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u3yi7155wby655ywm77.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4u3yi7155wby655ywm77.jpeg" alt="Image description" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A key detail here is using the &amp;lt;&lt;a href="https://react-admin-ee.marmelab.com/documentation/ra-relationships#referencemanytomanyinput" rel="noopener noreferrer"&gt;ReferenceManyToManyInpu&lt;/a&gt;t&amp;gt; component to handle the many-to-many relationship between entities and fields. It enables the selection of multiple fields from the &lt;code&gt;fields&lt;/code&gt; table their association with an entity. The &lt;code&gt;through&lt;/code&gt; and &lt;code&gt;using&lt;/code&gt; props define the join table and the columns that establish the relationship between the two tables.&lt;/p&gt;

&lt;p&gt;Similarly, I use the &lt;code&gt;&amp;lt;ReferenceManyToManyField&amp;gt;&lt;/code&gt; component to create the &lt;code&gt;entities&lt;/code&gt; list, displaying the associated fields as chips:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq2svw1qyk47sz6ghqci.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq2svw1qyk47sz6ghqci.jpeg" alt="Image description" width="800" height="602"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs6tpvbhqctezzdl56rk.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs6tpvbhqctezzdl56rk.jpeg" alt="Image description" width="720" height="191"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'll use a similar approach to define the CRUD views for the &lt;code&gt;fields&lt;/code&gt; and &lt;code&gt;field_types&lt;/code&gt; tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handling Dynamic Resources With React-Admin&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks to the SQL triggers I set up earlier, when a user changes the CMS configuration by altering the &lt;code&gt;entities&lt;/code&gt;, &lt;code&gt;fields&lt;/code&gt;, or &lt;code&gt;field_types&lt;/code&gt; tables, the corresponding entity tables are automatically created, updated, or deleted.&lt;/p&gt;

&lt;p&gt;Now, how can I define CRUD views for these entities? Since the tables are created dynamically, I cannot explicitly define the resources in the &lt;code&gt;&amp;lt;Admin&amp;gt;&lt;/code&gt; component. Instead, I need to generate the resources dynamically based on the data from the &lt;code&gt;entities&lt;/code&gt; and &lt;code&gt;fields&lt;/code&gt; tables.&lt;/p&gt;

&lt;p&gt;To achieve this, I’ll create a query that fetches the entities and fields from the API. I'll use Supabase's ability to join data from several tables with its &lt;code&gt;select&lt;/code&gt; function. I'll store the result in a React context. The App component will then use this context to generate the resources dynamically.&lt;/p&gt;

&lt;p&gt;Below is the &lt;code&gt;&amp;lt;DynamicResourceProvider&amp;gt;&lt;/code&gt; implementation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8kmz6diibegomnne1gi.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv8kmz6diibegomnne1gi.jpeg" alt="Image description" width="800" height="1280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I can wrap the entire application within this context provider. It's essential to ensure that &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt; is a child of the &lt;code&gt;&amp;lt;DynamicResourceProvider&amp;gt;&lt;/code&gt; since the context will be utilized within the &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7laze354ytkkei7ppzd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7laze354ytkkei7ppzd.jpeg" alt="Image description" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, I can consume the context to render one Resource for each entity within the &lt;code&gt;&amp;lt;Admin&amp;gt;&lt;/code&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wfu7mvtm1p4gguqqs9u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8wfu7mvtm1p4gguqqs9u.jpeg" alt="Image description" width="800" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;&amp;lt;Admin /&amp;gt;&lt;/code&gt; component now creates one menu entry per entity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frl1xp7ucw2usex9otr27.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frl1xp7ucw2usex9otr27.jpeg" alt="Image description" width="720" height="188"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generating List And Form Components With Dynamic Fields&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next, I need to generate dynamic &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;edit&lt;/code&gt;, and &lt;code&gt;create&lt;/code&gt; components for each resource. That's the purpose of the &lt;code&gt;DynamicResourceList&lt;/code&gt;, &lt;code&gt;DynamicResourceEdit&lt;/code&gt;, and &lt;code&gt;DynamicResourceCreate&lt;/code&gt; components.&lt;/p&gt;

&lt;p&gt;In these components, I need to map PostgreSQL types to corresponding React-Admin fields and inputs. For instance, a text field can be represented as a &lt;code&gt;&amp;lt;TextField&amp;gt;&lt;/code&gt; in the List component and a &lt;code&gt;&amp;lt;TextInput&amp;gt;&lt;/code&gt; in the Form component.&lt;/p&gt;

&lt;p&gt;I create utility functions to manage this mapping, such as for the inputs:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8x1z2i28nrausfa73xa.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8x1z2i28nrausfa73xa.jpeg" alt="Image description" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can then use it to generate the components for the dynamic resources:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gormla0cnpikq593dm5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gormla0cnpikq593dm5.jpeg" alt="Image description" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that's it! I can now enter data in entity tables using the admin interface.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folpc2kd26rxkjaik6epg.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Folpc2kd26rxkjaik6epg.jpeg" alt="Image description" width="720" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This proof of concept has some limitations that need to be addressed.&lt;/p&gt;

&lt;p&gt;One issue I encountered is with React-Admin’s efficient cache management, which helps reduce the number of API calls. However, I need to invalidate the cache for the query fetching the dynamic resources whenever an entity is created, updated, or deleted. To resolve this, I specify an &lt;code&gt;onSuccess&lt;/code&gt; callback that triggers after performing any of these actions. For example, in the &lt;code&gt;EntitiesCreate&lt;/code&gt; component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flltmgjg4d456qcylsck0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flltmgjg4d456qcylsck0.jpeg" alt="Image description" width="800" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another limitation is the lack of support for relationships between entities. For instance, I might need a &lt;code&gt;comment&lt;/code&gt; entity with a &lt;code&gt;post_id&lt;/code&gt; foreign key to associate a comment with a post. This can be probably addressed by adding a &lt;code&gt;reference_entities&lt;/code&gt; column to the entities table to store foreign key relationships between entities. Implementing this would require updating the triggers to create the necessary foreign keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this post, I showed how to build a CMS proof of concept using React-Admin and Supabase. By combining SQL triggers for managing the database schema, React-Admin for the administration interface, and a custom context provider to handle dynamic resources, I was able to create a flexible and dynamic CMS with minimal code.&lt;/p&gt;

&lt;p&gt;This proof of concept can be expanded with additional features not covered here, such as authentication or mandatory fields. It also has some limitations, as noted earlier. Nevertheless, it offers a strong foundation for building a CMS with React-Admin and Supabase. More importantly, it highlights how React-Admin can effectively handle dynamic resources.&lt;/p&gt;

&lt;p&gt;The full code for this proof of concept is on GitHub: &lt;a href="https://github.com/marmelab/react-admin-cms" rel="noopener noreferrer"&gt;marmelab/react-admin-cms&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>cms</category>
      <category>react</category>
    </item>
    <item>
      <title>5 Essential Practices for Building Efficient Gen AI Programs</title>
      <dc:creator>react-admin</dc:creator>
      <pubDate>Tue, 12 Nov 2024 13:35:22 +0000</pubDate>
      <link>https://forem.com/react-admin/5-essential-practices-for-building-efficient-gen-ai-programs-3odn</link>
      <guid>https://forem.com/react-admin/5-essential-practices-for-building-efficient-gen-ai-programs-3odn</guid>
      <description>&lt;p&gt;Building efficient Gen AI programs requires more than just deploying a model — it demands a structured approach that balances performance, cost, and reliability. Understanding how to optimize workflows will make the difference between a well-functioning AI solution and one that drains resources. In this article, we’ll explore the key principles and best practices to help you maximize the efficiency of your Gen AI programs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build efficient Gen AI programs?
&lt;/h2&gt;

&lt;p&gt;1) Break the problem into smaller sub-tasks. 🏗️&lt;/p&gt;

&lt;p&gt;First, not all tasks require the most powerful LLM. Break the problem into smaller sub-tasks, use traditional programming techniques for data-heavy processes and user interface rendering, leverage small models for simpler AI tasks like sentiment analysis and spelling correction, and reserve the heavy lifting for larger models only when necessary.&lt;/p&gt;

&lt;p&gt;2) Implement LLM Orchestration. ⚙️&lt;/p&gt;

&lt;p&gt;Orchestration becomes crucial because LLMs are slow. Streaming, parallelism, and speculative execution can help speed up the process. For instance, while Midjourney shows a half-finished image as it generates, you still need to wait for the full result to evaluate it. The same goes for using LLMs to determine function parameters—you can’t execute the function until all parameters are ready.&lt;/p&gt;

&lt;p&gt;3) Establish robust error handling. 🚨&lt;/p&gt;

&lt;p&gt;Error handling is another essential component. You need mechanisms to detect when an LLM has produced a wrong or harmful output and decide whether to retry, adjust, or abandon that task. Many commercial LLMs have built-in safety layers that cut off offensive content during streaming (a.k.a. safeguarding).&lt;/p&gt;

&lt;p&gt;4) Evaluate your LLM setup regularly. 🕵🏻&lt;/p&gt;

&lt;p&gt;Evaluating the effectiveness of an LLM setup is like running continuous integration tests. You need robust evaluation to ensure reliability, given the numerous ways to configure and prompt an LLM. The eval systems may even involve other LLMs, which require their own tuning. Just like with automated tests, you need to rerun the evaluation benchmarks after every configuration change to catch issues before they reach customers. &lt;/p&gt;

&lt;p&gt;5) Monitor costs closely. 💸&lt;/p&gt;

&lt;p&gt;Monitoring costs is crucial too—FinOps must be part of Gen AI from day one. Whether using a hosted LLM or running one on your own GPUs, managing expenses is a significant challenge, especially as you scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The good news? You don't need to be an AI researcher to use LLMs. Off-the-shelf models, standardized APIs, and abundant tutorials have made it possible for traditional developers to build Gen AI applications.&lt;/p&gt;

&lt;p&gt;Incorporating Gen AI into applications opens up vast possibilities, but it requires oversight to ensure efficiency. As AI technology continues to evolve, mastering these fundamental practices will empower developers and organizations to build robust, scalable, and sustainable Gen AI solutions.&lt;/p&gt;

&lt;p&gt;Want to read more from us? Hop over to our &lt;a href="https://marmelab.com/en/blog" rel="noopener noreferrer"&gt;blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>genai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Building a Complete React CRM App with Atomic CRM 🛠️</title>
      <dc:creator>react-admin</dc:creator>
      <pubDate>Fri, 06 Sep 2024 14:20:00 +0000</pubDate>
      <link>https://forem.com/react-admin/building-a-complete-react-crm-app-with-atomic-crm-107o</link>
      <guid>https://forem.com/react-admin/building-a-complete-react-crm-app-with-atomic-crm-107o</guid>
      <description>&lt;p&gt;Every company has unique CRM needs, but not many can afford a custom solution.&lt;/p&gt;

&lt;p&gt;To solve this problem we built &lt;a href="https://marmelab.com/atomic-crm/" rel="noopener noreferrer"&gt;Atomic CRM&lt;/a&gt; — an open-source CRM framework made for developers and designed to be user-friendly for everyone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q9kv3hovy4z7yby7su8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q9kv3hovy4z7yby7su8.jpeg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every Company Needs A Custom CRM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For small and medium-sized businesses, finding the right CRM solution can be difficult, and building an in-house system is often the best option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Off-the-shelf solutions&lt;/strong&gt; like SuiteCRM, Odoo, OroCRM, or Vtiger frequently fall short of addressing specific business needs, forcing teams to use workarounds or make compromises. These platforms tend to be complex, require expensive integrations, or come with &lt;strong&gt;high costs&lt;/strong&gt; that don’t match the value they provide.&lt;/p&gt;

&lt;p&gt;For companies with straightforward but unique CRM requirements, &lt;strong&gt;developing an in-house solution&lt;/strong&gt; is often more cost-effective. We've experienced this ourselves. Seeing these challenges, we created a simple CRM tailored to our own needs, named Atomic CRM. But why should every company reinvent the wheel?&lt;/p&gt;

&lt;p&gt;We believe that Atomic CRM offers a powerful foundation that can save businesses time and resources. That’s why we’re open-sourcing it today under the permissive MIT license, empowering companies to build a custom CRM solution at a fraction of the cost: &lt;a href="https://github.com/marmelab/atomic-crm" rel="noopener noreferrer"&gt;marmelab/atomic-crm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Features Everybody Needs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Atomic CRM covers all the essential CRM features and can be used straight out of the box to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📇 &lt;strong&gt;Organize Contacts&lt;/strong&gt;: Keep all your contacts in one accessible place, making it convenient to find and manage your contacts. The intuitive interface ensures that you have the right information at your fingertips whenever you need it.&lt;/li&gt;
&lt;li&gt;📝 &lt;strong&gt;Take Notes&lt;/strong&gt;: Capture important insights effortlessly with the note-taking feature.&lt;/li&gt;
&lt;li&gt;⏰ &lt;strong&gt;Create Tasks &amp;amp; Set Reminders&lt;/strong&gt;: Stay on top of your to-do list with Atomic CRM’s task management feature. Create tasks, set reminders, and never miss a follow-up again.&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Manage Deals&lt;/strong&gt;: Visualize your sales pipeline effectively in a Kanban board. This feature helps you track deal progress, prioritize tasks and close deals faster, ultimately boosting your team's productivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tixf1cv3u83xexaphqb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tixf1cv3u83xexaphqb.jpeg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, CRMs don’t operate in isolation — they need to integrate with other enterprise tools. Atomic CRM enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔄 &lt;strong&gt;Import &amp;amp; Export Data&lt;/strong&gt;: Easily transfer contacts and other important information in and out of the system, ensuring smooth integration with your existing workflows.&lt;/li&gt;
&lt;li&gt;✉️ &lt;strong&gt;Capture Emails&lt;/strong&gt;: Integrate your email communications seamlessly. By including Atomic CRM in your email's CC, the system automatically saves the correspondence as a note.&lt;/li&gt;
&lt;li&gt;🛠️ &lt;strong&gt;API Integration&lt;/strong&gt;: Connect Atomic CRM with other systems effortlessly through its robust API. This integration provides a cohesive and unified data environment, making Atomic CRM a central hub for all your customer relationship management needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In summary, Atomic CRM is packed with all the required features to streamline customer relationship management, allowing developers to focus on implementing custom business logic. By handling the basics, it frees up time for your team to create solutions that deliver real value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready For Customization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With a simple relational data model, developers can easily modify the system to store additional data. Its component-based architecture allows for replacing or customizing any part of the application, giving developers full control over the user experience. Built using React and &lt;a href="https://marmelab.com/react-admin/" rel="noopener noreferrer"&gt;react-admin&lt;/a&gt;, two widely supported frameworks, it comes with a rich library of pre-built components ready for use.&lt;/p&gt;

&lt;p&gt;Atomic CRM supports adding custom pages, enabling scalability as your business evolves. Developers can integrate custom logos and themes to match the company's brand identity.&lt;/p&gt;

&lt;p&gt;Designed to be customized directly through code, the framework avoids the need for a complex customization UI. This makes the codebase clean and simple, allowing developers to modify and extend it easily to meet specific requirements.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9ve9c4ucz6um2p5my7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9ve9c4ucz6um2p5my7x.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deploy Anywhere&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Atomic CRM leverages &lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt; for data storage and backend APIs, combined with a static single-page application for the UI. This simple architecture allows you to host it for free almost anywhere. The default deployment, using Supabase.com and GitHub Pages, can be set up in just 5 minutes with a single command.&lt;/p&gt;

&lt;p&gt;Since customer data is often sensitive, many companies require strict control over their CRM hosting. With Atomic CRM, you can store customer data on your own infrastructure, behind your firewall, and integrate your own authentication system. It supports single sign-on with popular providers like Google, Azure, and Auth0, ensuring secure login using existing enterprise accounts-with no additional charge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsa10bn2clk1ge6ohwaw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftsa10bn2clk1ge6ohwaw.jpeg" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Whether you're a startup or a large business looking to improve customer management, Atomic CRM is a great starting point. It offers all the key features you need and is easy to customize, letting developers build a CRM that fits their exact needs. Best of all, it's free.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://marmelab.com/atomic-crm/" rel="noopener noreferrer"&gt;Atomic CRM website&lt;/a&gt; or dive straight into the &lt;a href="https://github.com/marmelab/atomic-crm" rel="noopener noreferrer"&gt;code&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We're curious to hear about your experience! What challenges have you faced in developing CRM solutions? What functionalities are you looking for in a framework to overcome those challenges?&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>react</category>
      <category>crm</category>
    </item>
    <item>
      <title>Are No-Code Tools A Good Choice For Developers?</title>
      <dc:creator>react-admin</dc:creator>
      <pubDate>Fri, 17 Feb 2023 12:00:00 +0000</pubDate>
      <link>https://forem.com/react-admin/are-no-code-tools-a-good-choice-for-developers-12e0</link>
      <guid>https://forem.com/react-admin/are-no-code-tools-a-good-choice-for-developers-12e0</guid>
      <description>&lt;p&gt;Developers often use low-code tools like react-admin to increase their development speed. But what about no-code tools? How far can they get and are they nice to use?&lt;/p&gt;

&lt;p&gt;As an experiment, I decided to try replicating the React-Admin demo using a no-code tool. This e-commerce demo shows an admin for a fictional poster shop. I chose the most popular no-code platform, Retool. My goal is to see if it is possible to replicate the functionality of a developer tool using a no-code approach. Stay tuned to see how it turns out!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffca3hz812rhls1ajnmcf.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffca3hz812rhls1ajnmcf.jpg" alt="React-Admin demo" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Working With A Fake API
&lt;/h1&gt;

&lt;p&gt;Before diving into no-code, I need to set up an API that Retool can use. To do this, I have to write some code - but it's for testing purposes, and it should be the only code in this post. I use the e-commerce fake data generator published with react-admin, along with JSON Server to create the API, and ngrok to expose it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frm53r8ckgi2n9jk174c6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frm53r8ckgi2n9jk174c6.png" alt="Code" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I have an API running locally and accessible from the outside via a ngrok.io URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6conmz3bksdfgcuvxyba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6conmz3bksdfgcuvxyba.png" alt="Code" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Setting Up The API Connection
&lt;/h1&gt;

&lt;p&gt;To access my API in Retool, I create a new resource of type REST API using the ngrok URL. I name this resource "Posters Galore".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8i6zjbz2ap6vufy9mlaa.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8i6zjbz2ap6vufy9mlaa.jpeg" alt="Setting Up The API Connection" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating The First Component
&lt;/h1&gt;

&lt;p&gt;It's time to start building the first component: the leftmost block of the top row in the demo dashboard, which displays the monthly revenue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrpw43o8qcm5caui8d1j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrpw43o8qcm5caui8d1j.jpg" alt="First Component" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To begin, I create a new app in Retool and name it "Dashboard". To display data in the component, I need to set up a query that retrieves the commands for the last 30 days. I name this query "commands_for_last_30_days" and use the "Posters Galore" resource and the &lt;code&gt;commands&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;In order to filter the results to the last 30 days, I need to set the &lt;code&gt;date_gte&lt;/code&gt; URL parameter. For the value, Retool allows me to use a JavaScript expression with some included libraries like &lt;code&gt;moment&lt;/code&gt;. This expression will return the date 30 days ago: &lt;code&gt;{{moment().startOf('day').subtract(30, 'days')}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ssdapkysmxejclv33ba.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ssdapkysmxejclv33ba.jpeg" alt="Commands" width="800" height="126"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I add a &lt;code&gt;Statistic&lt;/code&gt; component to my app. To allow for more customization, such as changing the background color, I wrap it in a &lt;code&gt;Container&lt;/code&gt; component. Using the sidebar, I can easily configure the &lt;code&gt;Statistic&lt;/code&gt; component to meet my needs, such as adding a label and icon.&lt;/p&gt;

&lt;p&gt;To set the displayed value, I select the &lt;code&gt;commands_for_last_30_days&lt;/code&gt; query and use a JavaScript expression to compute the sum of the total field: &lt;code&gt;{{commands_for_last_30_days.data.reduce((acc, command) =&amp;gt; acc + command.total, 0)}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first component that fetches the API and renders the result is now ready!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb77pgct3ulqrcw5hyreu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb77pgct3ulqrcw5hyreu.jpeg" alt="first component that fetches the API " width="380" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can follow the same process to create the component that displays the number of orders in the last 30 days with the following displayed value: &lt;code&gt;{{commands_for_last_30_days.data.length}}&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Displaying A List Of Records
&lt;/h1&gt;

&lt;p&gt;The next component I want to build is the list of pending reviews.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdwijshzkshfhp4dfcaa.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzdwijshzkshfhp4dfcaa.jpg" alt="List Of Records" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To do this, I need to create a &lt;code&gt;pending_reviews&lt;/code&gt; query that retrieves all the reviews with a status of "pending". Then, I use a &lt;code&gt;List view&lt;/code&gt; component to display these reviews. To set the number of rows in the list view, I use the following expression: &lt;code&gt;{{pending_reviews.data.length}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then, I can put in the &lt;code&gt;List view&lt;/code&gt; the compoents that will be used to display each row. In this case, it is an &lt;code&gt;Avatar&lt;/code&gt; component and a &lt;code&gt;Text&lt;/code&gt; component. In a row, we can use the i variable to access data of the current row. For example, to display the comment of the review in the Text component, I use the following expression: &lt;code&gt;{{pending_reviews.data[i].comment}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To display the avatar, it is a little bit more complex because it is a relationship between reviews and customers. The &lt;code&gt;pending_reviews&lt;/code&gt; query does not directly contain the customer's avatar. To get the customer's avatar, I need to create a new query that retrieves all customers with the ID of the review. I name this query &lt;code&gt;customers&lt;/code&gt; and use the &lt;code&gt;customers&lt;/code&gt; endpoint. I can now use the &lt;code&gt;customers&lt;/code&gt; query to get the avatar of the customer who write the review in the &lt;code&gt;Avatar&lt;/code&gt; component using the following expression: &lt;code&gt;{{customers.data.find((customer) =&amp;gt; customer.id === pending_reviews.data[i].customer_id).avatar}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The list of pending reviews is now ready!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiz55wvyet363pjps9gyz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiz55wvyet363pjps9gyz.jpeg" alt="list of pending reviews" width="370" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only problem is that it calls the API once for the reviews, and once for each review to fetch the author...&lt;/p&gt;

&lt;h1&gt;
  
  
  Displaying a chart
&lt;/h1&gt;

&lt;p&gt;One key benefit of using no-code tools is their ability to effortlessly create complex elements such as charts, which can be challenging to accomplish with traditional coding methods. Let's try this on the chart displaying the revenue from the last 30 days.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe81g5gw83ws31q4q0p99.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe81g5gw83ws31q4q0p99.jpg" alt="chart" width="800" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All I need to do is create the query &lt;code&gt;invoices_for_last_30_days&lt;/code&gt; and use the chart component. With the sidebar, I can then select the &lt;code&gt;invoices_for_last_30_days&lt;/code&gt; query as the data source, choose the &lt;code&gt;Line chart&lt;/code&gt; type, and select to display only the &lt;code&gt;total&lt;/code&gt; data from the query.&lt;/p&gt;

&lt;p&gt;I have a problem when my query returns several invoices for the same day. To group invoices by day, I can directly transform the result of the query with some custom JavaScript code. For this particular case, I can accomplish this task with a &lt;code&gt;reduce&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpllmav8fkkjx4fnwdfw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpllmav8fkkjx4fnwdfw.jpeg" alt="Code for chart" width="800" height="176"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart is now ready!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmw3e1z3qaobqan72095.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmw3e1z3qaobqan72095.jpeg" alt="chart" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Except I had to use JavaScript... Can a non-developer do that?&lt;/p&gt;

&lt;h1&gt;
  
  
  Building a Multi-Page App
&lt;/h1&gt;

&lt;p&gt;Creating a dashboard is nice but I need to have other views in my admin app. The react-admin demo is a complete app showing orders, invoices, products, customers, reviews, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp997tipnxwwh0cn4t07q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp997tipnxwwh0cn4t07q.jpg" alt="Multi-Page App" width="800" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, Retool does not allow to create multiple pages in a single app. To solve this problem, I have to create a new app.&lt;/p&gt;

&lt;p&gt;Let's create a new app for the invoices list, that I name "Invoices list". Then, I add a header to my app with the left sidebar. By default, the header contains a &lt;code&gt;Navigation&lt;/code&gt; component. This is exactly what I need! With the right sidebar, I can easily configure each link of the navigation. For example, I can set the link to the dashboard app and the label to "Dashboard".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznzme33zzi6ckdmxh13t.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznzme33zzi6ckdmxh13t.jpeg" alt="Dashboard" width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This navigation has to be added in each separate app of my "multi-page" app. So, I also add it to the Dashboard app. Now, I can navigate between the two apps.&lt;/p&gt;

&lt;p&gt;TIP:&lt;/p&gt;

&lt;p&gt;If some components have to be shared between several apps, it is probably better to use the Module features provided by Retool. The queries can also be shared by exporting them to the "Query library".&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating A Table View
&lt;/h1&gt;

&lt;p&gt;To create the invoices view, I first create a new query called &lt;code&gt;invoices&lt;/code&gt; to retrieve all invoices, and use it in a &lt;code&gt;Table&lt;/code&gt; component. Using the right sidebar, I can easily customize the column labels and select the fields to display in the table, such as hiding the &lt;code&gt;tax_rate&lt;/code&gt;. The &lt;code&gt;Table&lt;/code&gt; component also enables user to choose the sort field and add filters.&lt;/p&gt;

&lt;p&gt;Currently, the order ID is visible in the table. To enhance the user experience, it would be great to directly display the order reference, similar to how the React Admin ReferenceField works. To achieve this, I have to create another query, &lt;code&gt;orders&lt;/code&gt; to retrieve all orders. I can then add an "Order" column to the table and use JavaScript with the keyword &lt;code&gt;currentRow&lt;/code&gt; to obtain the reference of the corresponding order ID:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg9xd3n2xmzp8nrzmmxj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsg9xd3n2xmzp8nrzmmxj.png" alt="code" width="800" height="152"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would like to achieve a similar outcome with the customer ID by directly displaying the customer's avatar in the table. Unfortunately, it appears that it is not possible to nest a component like the &lt;code&gt;Avatar&lt;/code&gt; component within the &lt;code&gt;Table&lt;/code&gt; component. However, it is possible to use &lt;code&gt;Image URL&lt;/code&gt; as the column type. To display the avatar, I can use the following expression:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4p1sidkrlwlutqi92z2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx4p1sidkrlwlutqi92z2.png" alt="code" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, I can create another column to display the customer's name by using the following expression:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyupotuyps3qx3a90kgv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpyupotuyps3qx3a90kgv.png" alt="code" width="800" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The invoices table is now ready!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgog4fn5aex42m6g7ycym.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgog4fn5aex42m6g7ycym.jpeg" alt="Invoices table" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But again, without basic coding knowledge, I would be very limited in terms of customization.&lt;/p&gt;

&lt;h1&gt;
  
  
  Adding Actions To A Table
&lt;/h1&gt;

&lt;p&gt;In the invoices list, I would like to have actions to edit or delete an invoice. To do this, I can use the &lt;code&gt;Actions&lt;/code&gt; feature of the &lt;code&gt;Table&lt;/code&gt; component. With the right sidebar, I can add a new action which I name &lt;code&gt;Delete&lt;/code&gt; and select a query to run for this action. In this case, I create the &lt;code&gt;remove_invoice&lt;/code&gt; query. In the query endpoint, I can access to the table data, so my endpoint is invoices/&lt;code&gt;{{ table1.data[i].id }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As this action is deleting an invoice, I want to refresh the table once the action is executed. I can achieve this by using the "Event handlers" of the query. I set it to trigger the &lt;code&gt;invoices&lt;/code&gt; query upon the successful execution of the &lt;code&gt;remove_invoice&lt;/code&gt; query.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjller6ytjshaanifp98.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjller6ytjshaanifp98.jpeg" alt="Event handlers" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Success! But is the notion of event handlers and triggers really a "no-code" notion?&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;With the creation of a dashboard and a table view that includes a delete feature, my small admin application is now complete. It is only a subset of the react-admin e-commerce demo, but the main building blocks are there.&lt;/p&gt;

&lt;p&gt;The main building blocks, but not the creation and edition features! That's maybe for another post.&lt;/p&gt;

&lt;p&gt;In my opinion, as a JavaScript developer, this no-code tool has left me with mixed feelings. While it may be well-suited for building simple applications, more complex or customized projects will require the use of JS code. At the start of my experiment, I did not anticipate the need to write Javascript for nearly every component I had in mind.&lt;/p&gt;

&lt;p&gt;In terms of development speed, I do not find it to be faster than using a framework such as React-Admin. This may be due to my background as a developer and my first-time use of the tool. For example, with React-Admin, the same type of table view can be created with just 3 lines of code.&lt;/p&gt;

&lt;p&gt;Even for non-developers, I believe that it may not be enough user-friendly, as it requires a basic understanding of Javascript and how APIs work. For instance, creating a query requires knowledge of how to write a query endpoint, which may not be obvious for those without a development background.&lt;/p&gt;

&lt;p&gt;To conclude, I think it requires the knowledge of a developer to tweak no-code tools such as ReTool enough to build moderately complex apps. And developers are more productive with low-code tools... So I wouldn't recommend it, except for simple dashboards.&lt;/p&gt;

&lt;p&gt;Did you like this article? Share it!&lt;/p&gt;

&lt;p&gt;✍️ Written by our team member Thibault Barrat :)&lt;/p&gt;

</description>
      <category>html</category>
      <category>css</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
