<?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>DEV Community: Ronan McQuillan</title>
    <description>The latest articles on DEV Community by Ronan McQuillan (@ronan_mcquillan_628bdb9e3).</description>
    <link>https://dev.to/ronan_mcquillan_628bdb9e3</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%2F873630%2Fa5d68523-4dfa-4cdd-a9a2-eb5a61805926.jpg</url>
      <title>DEV Community: Ronan McQuillan</title>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ronan_mcquillan_628bdb9e3"/>
    <language>en</language>
    <item>
      <title>REST API Authentication | 4 Methods</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Fri, 11 Aug 2023 10:04:00 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/rest-api-authentication-4-methods-lcb</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/rest-api-authentication-4-methods-lcb</guid>
      <description>&lt;p&gt;Understanding the different methods of API authentication is a core competence for any would-be developer, data professional, or anyone else who wants to connect to external tools, data sources, or other resources.&lt;/p&gt;

&lt;p&gt;Nowadays, APIs are fundamental to the way we build, integrate, and use all kinds of digital tools - whether this is building custom software from scratch - or simply connecting existing SaaS platforms to create streamlined workflows.&lt;/p&gt;

&lt;p&gt;So, &lt;em&gt;you&lt;/em&gt; need to know how to use the different authentication methods that vendors could implement for their APIs. &lt;/p&gt;

&lt;p&gt;Today, we’re going to cover everything you need to know about working with the most common REST API authentication methods.&lt;/p&gt;

&lt;p&gt;By the end of this guide, you’ll be fully familiar with how to access data and resources from just about any API. We’ll also wrap up with a few extra considerations for managing API authentication with extra levels of security.&lt;/p&gt;

&lt;p&gt;Here goes.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/zeMVcx59IsE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What is REST API authentication?
&lt;/h2&gt;

&lt;p&gt;API authentication is the process of verifying the identity of a user or other actor - in order to confirm that they have the necessary permissions for whatever they’re trying to do via an API. &lt;/p&gt;

&lt;p&gt;Specifically, authentication allows API owners to do three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the identity of a client or user.&lt;/li&gt;
&lt;li&gt;Enable authorized clients and users can access the API.&lt;/li&gt;
&lt;li&gt;Prevent unauthorized access.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is very tightly linked to another idea - &lt;em&gt;authorization&lt;/em&gt;. Following on from authentication, this is how vendors govern which elements of their API clients and users can access. &lt;/p&gt;

&lt;p&gt;Authorization is used for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Granting access and exposure to particular resources or data for different users.&lt;/li&gt;
&lt;li&gt;Governing what actions different users and clients can take with our API.&lt;/li&gt;
&lt;li&gt;Otherwise enforce defined access control policies.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the perspective of someone &lt;em&gt;accessing&lt;/em&gt; an API, we’re not too worried about &lt;em&gt;how&lt;/em&gt; vendors implement authorization after authentication. But, it’s worth being aware that they &lt;em&gt;might&lt;/em&gt; use our authentication data in order to determine what resources and actions we can access.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I know what authentication method to use?
&lt;/h2&gt;

&lt;p&gt;We can actually attack this question from two slightly different angles. On the one hand, there’s how each particular API is built. On the other hand, there’s the kind of solution that we’re trying to build.&lt;/p&gt;

&lt;p&gt;Let’s think about the first point first, because it’s a relatively straightforward one. Individual vendors are going to require you to use different authentication methods. &lt;/p&gt;

&lt;p&gt;Some might give you the flexibility to choose from multiple methods, while others will be more opinionated, and insist on using a specific method. Ultimately, there’s not a lot we can do here.&lt;/p&gt;

&lt;p&gt;If a vendor only offers access through a particular authentication method, then we’ll need to play ball if we want to use their API.&lt;/p&gt;

&lt;p&gt;The other side of the equation requires us to think about the actual &lt;em&gt;solution&lt;/em&gt; we’re trying to create with whatever API we’re accessing.&lt;/p&gt;

&lt;p&gt;In the most generic sense, this comes down to two different decisions. The first is whether we want our API client to authenticate &lt;em&gt;statically&lt;/em&gt; or &lt;em&gt;dynamically&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The difference is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static authentication&lt;/strong&gt; means that the same authentication data is used, whoever is using our solution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic authentication&lt;/strong&gt; means that our solution will store and leverage &lt;em&gt;different&lt;/em&gt; authentication information, depending on who the end user is - or even using some other contextual conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The other thing we want to think about is whether we need to authenticate &lt;em&gt;globally&lt;/em&gt; or on a &lt;em&gt;per-request&lt;/em&gt; basis from our API client.&lt;/p&gt;

&lt;p&gt;In other words, choosing between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All requests from our client using the same authentication credentials.&lt;/li&gt;
&lt;li&gt;Using different credentials for individual API requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s no better or worse here - you just need to be aware of these distinctions in order to make an informed choice about how you configure your API connections.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 methods for REST API authentication
&lt;/h2&gt;

&lt;p&gt;With that bit of theory out of the way, let’s dive into the four most common REST API authentication methods.&lt;/p&gt;

&lt;p&gt;Just a quick note on our examples here. We’re sending a GET request that sends and instruction to an API to retrieve data bout our users. To do this, we’ve created a Budibase app, with REST API selected as its data source.&lt;/p&gt;

&lt;p&gt;We’re going to look at what each authentication method looks like within Budibase, but the basic principles are the same whichever API client you’re using. &lt;/p&gt;

&lt;p&gt;We’ll also check out what they would look like in a hard-coded solution for the sake of completion.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. API keys as headers
&lt;/h3&gt;

&lt;p&gt;First, we have API authentication via HTTP &lt;em&gt;headers&lt;/em&gt;. The headers section of an API request is a series of &lt;em&gt;key/value pairs&lt;/em&gt; that are used to provide extra information for the server about whatever we’re trying to do.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;key&lt;/em&gt; is the name of the attribute we’re providing and the &lt;em&gt;value&lt;/em&gt; is the specific piece of data that corresponds to this.&lt;/p&gt;

&lt;p&gt;Authentication is actually one of the most common use cases for headers. Most of the time, this means using a &lt;em&gt;key&lt;/em&gt; called &lt;strong&gt;x-api-key&lt;/strong&gt; and giving it a value of whatever our unique API key is.&lt;/p&gt;

&lt;p&gt;Naming conventions can vary here, so make sure you’re using whatever attribute the vendor specifies in their documentation.&lt;/p&gt;

&lt;p&gt;So, if our API key was &lt;em&gt;super-secret-key&lt;/em&gt;, for example, our hard-coded headers might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

“x-api-key”: “super-secret-key”

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, we aren’t necessarily going to want to hard code solutions, so let’s look at how we can use API headers for authentication in Budibase.&lt;/p&gt;

&lt;p&gt;We’ve actually got two options for adding headers to queries. First, we can create a new query, and add key/value pairs under the &lt;em&gt;headers&lt;/em&gt; tab, like so:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941920%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_1_hkdvvx.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941920%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_1_hkdvvx.webp" title="REST API authentication" alt="REST API authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows us to stipulate authentication credentials on &lt;em&gt;query-by-query&lt;/em&gt; basis.&lt;/p&gt;

&lt;p&gt;Alternatively, we can also add &lt;em&gt;global headers&lt;/em&gt;. To do this, head to the configuration options for your REST data source, and hit the headers tab again:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FRest_api_auth_2_fwdgac.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FRest_api_auth_2_fwdgac.webp" title="Auth headers" alt="Auth headers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With both options, if we simply enter our &lt;em&gt;API&lt;/em&gt; key in the text box provided, it will be applied statically. We can also use the lightening bolt icon to open Budibase’s &lt;em&gt;binding menu&lt;/em&gt; to create &lt;em&gt;dynamic&lt;/em&gt; values.&lt;/p&gt;

&lt;p&gt;For example, if we wanted to use environment variables, user accounts, or even form inputs as our API headers. Here’s what this would look like with a secure environment variable:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_3_z898xb.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_3_z898xb.webp" title="Dynamic auth" alt="Dynamic auth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out our in-depth guide to &lt;a href="https://budibase.com/blog/inside-it/api-headers/" rel="noopener noreferrer"&gt;API headers&lt;/a&gt; for more information on what these can be used for.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. API keys as query parameters
&lt;/h3&gt;

&lt;p&gt;Alternatively, some vendors will ask us to provide authentication details as &lt;em&gt;API parameters&lt;/em&gt;. Rather than within the headers sections, these are keys and values that are provided in our request URL or body.&lt;/p&gt;

&lt;p&gt;If we were using a URL parameter, our GET Users request would look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

https://example-api-url.com/v1/users**?apiKey=super-secret-key**

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, to add a URL parameter to a query we append the provided endpoint with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

?attributeName=value

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, we’ve got a couple of different options for how we want to do this in Budibase.&lt;/p&gt;

&lt;p&gt;We could add this manually to our request URL or under the &lt;em&gt;parameters&lt;/em&gt; tab for any given query. When we populate a parameter in one location, it will appear in the other - since these are actually the same thing:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_4_o1xesg.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_4_o1xesg.webp" title="REST API authentication" alt="REST API Authentication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also have the option of creating &lt;em&gt;bindable values&lt;/em&gt; with a &lt;em&gt;default value&lt;/em&gt; - and assigning these as parameters. &lt;/p&gt;

&lt;p&gt;For example, we could create a binding called &lt;em&gt;apiKey&lt;/em&gt; with a default value of &lt;em&gt;super-secret-key&lt;/em&gt;, but use front-end logic to assign different contextual values to this - giving us more flexibility over our queries.&lt;/p&gt;

&lt;p&gt;Here’s what this would look like in-situ:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_5_kfprwm.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_5_kfprwm.webp" title="REST API auth parameters" alt="URL parameters"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You might also like our guide titled &lt;a href="https://budibase.com/blog/inside-it/what-are-the-components-of-an-api/" rel="noopener noreferrer"&gt;&lt;em&gt;What are the Components of an API?&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Basic auth
&lt;/h3&gt;

&lt;p&gt;Some vendors might want us to use what’s known as &lt;em&gt;basic authentication&lt;/em&gt;. This means that instead of having a unique API key, clients and users must provide their a username and password.&lt;/p&gt;

&lt;p&gt;To configure this, first head to the &lt;em&gt;Authentication&lt;/em&gt; tab under your REST data source’s global settings and hit &lt;em&gt;add authentication&lt;/em&gt;:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_6_l1qfue.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941919%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_6_l1qfue.webp" title="Basic auth" alt="Basic auth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, we’ll see a modal where we can select &lt;em&gt;basic auth&lt;/em&gt;, and give our method a name. We can also enter our credentials here:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941920%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_7_gipjch.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941920%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_7_gipjch.webp" title="Auth credentials" alt="Auth credentials"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we’ve saved this, we can access it in individual queries using the &lt;em&gt;Auth&lt;/em&gt; dropdown:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941920%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_8_tvc23n.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941920%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_8_tvc23n.webp" title="select basic auth" alt="Select basic auth"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Bearer tokens
&lt;/h3&gt;

&lt;p&gt;Under the global authentication tab, we can also provide a bearer token, if this is what the vendor requires - for instance, if they perform authentication via OAuth tokens.&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941921%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_9_e7czvu.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1689941921%2Fcms%2Frest-api-authentication%2FREST_API_AUTH_9_e7czvu.webp" title="Auth token" alt="Auth token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have the option of simply adding our access token in manually as a string. Or, if we’re using the same SSO system as the API we’re trying to access, we can use the following binding to provide this dynamically, based on the current users:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ Current User.OAuthToken }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can use this in queries in exactly the same way as we did a second ago with basic auth.&lt;/p&gt;

&lt;p&gt;That’s it! Those are the four most common REST API authentication methods, along with how you can leverage them to connect to data sources in Budibase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other security practices for API authentication
&lt;/h2&gt;

&lt;p&gt;Authentication and authorization are crucial aspects of your security strategy. However, they’re only going to get you so far in the real world. &lt;/p&gt;

&lt;p&gt;Say for example some nefarious actor gets a hold of your API authentication credentials.&lt;/p&gt;

&lt;p&gt;You’d potentially be facing a very big problem, wouldn’t you?&lt;/p&gt;

&lt;p&gt;Here are some of the other strategies we can use to safeguard our security when using APIs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access control
&lt;/h3&gt;

&lt;p&gt;Access control is all about how we determine which users can do what with particular resources. It’s important to use an access control policy within our client applications that reflects and compliments the API resources we’re leveraging.&lt;/p&gt;

&lt;p&gt;For example, granting permissions based on different data sources, queries, automation rules, or front-end elements.&lt;/p&gt;

&lt;p&gt;Take a look at our guide to &lt;a href="https://budibase.com/blog/app-building/role-based-access-control/" rel="noopener noreferrer"&gt;role-based access control&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secure authentication protocols
&lt;/h3&gt;

&lt;p&gt;Secure authentication protocols such as OpenID, OAuth, and other proprietary SSO tools are an effective way to authenticate different users, applications, and other API clients. &lt;/p&gt;

&lt;p&gt;These help to provide an additional layer of security, while also giving IT administrators more control over who can access tools - and how.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforcing strong passwords
&lt;/h3&gt;

&lt;p&gt;Where possible, it’s vital to enforce strong passwords when dealing with user-generated authentication credentials. Of course, we’re constrained here in terms of what is provided for by vendors.&lt;/p&gt;

&lt;p&gt;Strong passwords are those that mix different character types, in long or even arbitrary strings.&lt;/p&gt;

&lt;p&gt;The best passwords are typically those that have been automatically generated by dedicated tools, within API clients, or other similar platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Leveraging encryption
&lt;/h3&gt;

&lt;p&gt;Encryption is used to keep database credentials, REST API authentication details, or other sensitive data secure. &lt;/p&gt;

&lt;p&gt;This helps us to eliminate several risk vectors, including hacks, data breaches, or malicious access to our business-critical data.&lt;/p&gt;

&lt;p&gt;For example, Budibase offers configurable encryption for in-application environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access monitoring
&lt;/h3&gt;

&lt;p&gt;Finally, monitoring who is accessing our technical resources, alongside how and when, is critical for maintaining the security of all kinds of solutions - including those that rely on external API requests.&lt;/p&gt;

&lt;p&gt;Regular auditing of our automation logs and user behavior ensures that we can identify new threats, unauthorized data access, or other critical security risks before they escalate and result in more serious outcomes.&lt;/p&gt;

&lt;p&gt;For more information on how to put what you’ve learned today into practice, check out our tutorial on how to create a &lt;a href="https://budibase.com/blog/tutorials/rest-api-gui/" rel="noopener noreferrer"&gt;REST API GUI&lt;/a&gt; with Budibase.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>What are the Components of a Web-Based REST API?</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Mon, 17 Jul 2023 12:20:47 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/what-are-the-components-of-an-api-39m1</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/what-are-the-components-of-an-api-39m1</guid>
      <description>&lt;p&gt;Knowing the components of an API is obviously important for setting them up to do what you want.&lt;/p&gt;

&lt;p&gt;Otherwise, how can you ever expect to use them correctly?&lt;/p&gt;

&lt;p&gt;Today, we’re covering everything you need to know. We’re going to take each of the constituent parts of a web-based REST APIs in turn and explain exactly what they are, what they do, and how you can use them.&lt;/p&gt;

&lt;p&gt;Along the way, we’ll think about real-world examples of &lt;em&gt;why&lt;/em&gt; each one is important, and the specific scenarios where they’re useful.&lt;/p&gt;

&lt;p&gt;But, before we dive in, let’s take a zoomed-out view of what we’re dealing with.&lt;/p&gt;

&lt;p&gt;So…&lt;/p&gt;

&lt;h2&gt;
  
  
  How do APIs work?
&lt;/h2&gt;

&lt;p&gt;Let’s start with a high-level overview of APIs. API stands for &lt;em&gt;application programming interface&lt;/em&gt;. Basically, this is a piece of code that allows two or more applications, data sets, or other nodes to communicate with each other.&lt;/p&gt;

&lt;p&gt;To ensure interoperability, APIs have to adhere to prescribed standards. There are a couple of specific protocols out there, but the basic idea is that - whichever tools we’re using - setting up an API should work the same way.&lt;/p&gt;

&lt;p&gt;But what do we mean by &lt;em&gt;communicate&lt;/em&gt;? Obviously, software tools don’t chat about their feelings.&lt;/p&gt;

&lt;p&gt;APIs allow &lt;em&gt;clients&lt;/em&gt; to make &lt;em&gt;requests&lt;/em&gt; to &lt;em&gt;servers&lt;/em&gt; - which then send back &lt;em&gt;responses&lt;/em&gt;. We’ll dive into each of these in a second.&lt;/p&gt;

&lt;p&gt;For now, the important thing is understanding that APIs are a way to request, retrieve, and share data between applications, within a defined framework.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ByUIqkJG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1685701489/cms/API-integration/API_use_https___nordicapis.com_apis-have-taken-over-software-development__y2sarz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ByUIqkJG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1685701489/cms/API-integration/API_use_https___nordicapis.com_apis-have-taken-over-software-development__y2sarz.webp" alt="What are the components of an API" title="what are the components of an API" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://nordicapis.com/apis-have-taken-over-software-development/"&gt;NordisCapis&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;To understand this in more depth, we need to circle back to our original question - what are the components of an API?&lt;/p&gt;

&lt;p&gt;Here goes!&lt;/p&gt;

&lt;h2&gt;
  
  
  API clients
&lt;/h2&gt;

&lt;p&gt;An &lt;em&gt;API&lt;/em&gt; client is the application that makes the initial request for data. This could be a web browser, a SaaS platform, an API management tool, a mobile app, or any other piece of software.&lt;/p&gt;

&lt;p&gt;The client is also responsible for bringing together all of the data that we need to &lt;em&gt;make&lt;/em&gt; the request - which we’ll explore in a second.&lt;/p&gt;

&lt;p&gt;When a request is made successfully, the &lt;em&gt;response&lt;/em&gt; is sent back to the client.&lt;/p&gt;

&lt;p&gt;We also need to consider the logic that determines &lt;em&gt;when&lt;/em&gt; a client sends a request. This could be based on a whole range of different triggers, depending on the specific workflow we’re dealing with.&lt;/p&gt;

&lt;p&gt;So, this could be user actions, like someone pressing a button, refreshing a screen, or filling in a form.&lt;/p&gt;

&lt;p&gt;Or, it could be based on automated logic - like a database query, time-based trigger, or some other condition being met in the back end.&lt;/p&gt;

&lt;h2&gt;
  
  
  API requests
&lt;/h2&gt;

&lt;p&gt;Next, let’s examine how &lt;em&gt;requests&lt;/em&gt; are made up.&lt;/p&gt;

&lt;p&gt;Remember, part of the idea behind APIs is that - whichever protocol you’re using - any platform that supports this should be able to interact with requests and responses in the same way. &lt;/p&gt;

&lt;p&gt;This is the basis of interoperability.&lt;/p&gt;

&lt;p&gt;Therefore, it follows logically that requests are going to need to follow a consistent format so that they can be handled the same way, regardless of the client and server involved.&lt;/p&gt;

&lt;p&gt;API requests are made up of five basic components. These are:&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoints
&lt;/h3&gt;

&lt;p&gt;The endpoint is the URL that an API request is directed at. This can be made up of a few distinct parts, depending on how our client and server are configured.&lt;/p&gt;

&lt;p&gt;The first is the actual destination of the resource that we’re trying to access. This includes the &lt;em&gt;domain&lt;/em&gt; of the server, along with any resources that the vendor has chosen to expose to us and the version of their API that we’re calling.&lt;/p&gt;

&lt;p&gt;So, say we were sending a call to an API for our local zoo. It lives at &lt;em&gt;api.the-zoo.com&lt;/em&gt; and we want to access data about lions. The vendor has created an API endpoint for us to request this called &lt;em&gt;/lions&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;We’re using the second version of their API.&lt;/p&gt;

&lt;p&gt;Our endpoint might be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.example.com/v2/lions&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You’ll need to check specific vendors’ documented API specifications to see what format you need to use and what endpoints are available.&lt;/p&gt;

&lt;p&gt;The endpoint can also be used to provide extra information - called &lt;em&gt;query parameters&lt;/em&gt; within our request. These need to be attributes that the server can accept within a particular endpoint so again, check the docs carefully.&lt;/p&gt;

&lt;p&gt;Say we only wanted to pull data about a &lt;em&gt;particular&lt;/em&gt; lion - whose name is &lt;em&gt;Franky&lt;/em&gt;. The zoo’s API allows us to do this using the &lt;em&gt;lionName&lt;/em&gt; parameter. We can add this to our endpoint like so:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.example.com/v2/lions?lionName=Franky&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But, this isn’t the only way to pass parameters in an API request. We’ll check this issue out more fully in a couple of minutes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Methods
&lt;/h3&gt;

&lt;p&gt;Before we get to that, we need to talk about &lt;em&gt;methods&lt;/em&gt;. A method is an instruction that tells the server what we expect it to do with the information that we’ve sent in our request.&lt;/p&gt;

&lt;p&gt;Specifically, what sort of operation we want it to perform on the resource we’re dealing with. API methods are essentially analogous to database operations.&lt;/p&gt;

&lt;p&gt;REST, the most dominant API protocol, uses standard HTTP methods. The most common of these include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GET&lt;/strong&gt; - for retrieving a data object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST&lt;/strong&gt; - for creating a new data object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PUT&lt;/strong&gt; - for creating a new data object &lt;em&gt;or&lt;/em&gt; altering an existing one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PATCH&lt;/strong&gt; - for modifying an existing object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DELETE&lt;/strong&gt; - for deleting existing objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, we created an endpoint earlier that targets any lions with the name Franky. If we sent a GET request to this endpoint, we might get back a JSON formatted data object that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

“lions”:{

​ “lionName”: “Franky”,

​ “age”: 16,

​ “adoptionDate”: 2016-02-02,

​ “favoriteFood”: “Steak”

​ }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we didn’t have any kind of filtering, the API request would return the same information for &lt;em&gt;all&lt;/em&gt; of the zoo’s lions.&lt;/p&gt;

&lt;p&gt;Say we wanted to change Franky’s favorite food. We’d probably use a PUT request - although some APIs might be set up to prefer for you to use PATCH - always read the docs.&lt;/p&gt;

&lt;p&gt;But, so far, we only have the components to select a row and say we want to update it. We don’t have anywhere to &lt;em&gt;add new values&lt;/em&gt; just yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parameters
&lt;/h3&gt;

&lt;p&gt;That’s where &lt;em&gt;parameters&lt;/em&gt; come in. These are arguments that we can pass to our API request. We’ve actually seen this already when we placed attributes in our URL in order to filter a GET request.&lt;/p&gt;

&lt;p&gt;Besides filtering, parameters can also be used to specify which attributes we want to retrieve, for authentication and user management, to provide new data in our request, or for any other data transfer purposes.&lt;/p&gt;

&lt;p&gt;There are a few different types of parameters that we need to be aware of. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path parameters&lt;/strong&gt; - Extensions to the URL to select specific attributes that we want to retrieve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query parameters&lt;/strong&gt; - For performing sort, filter, and pagination functions within our request URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header parameters&lt;/strong&gt;  - Data that we store in our request headers. More on this in a second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request body parameters&lt;/strong&gt; - Attributes that we place in our request body in order to add or modify data objects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, in order to successfully perform a PUT request on our &lt;em&gt;lions&lt;/em&gt; data, we’ll need to provide the new data in our request body. &lt;/p&gt;

&lt;p&gt;In our example, this would be Franky’s new favorite food.&lt;/p&gt;

&lt;h3&gt;
  
  
  Headers
&lt;/h3&gt;

&lt;p&gt;Before we look at the request body though, we need to think about &lt;em&gt;headers&lt;/em&gt;. This is a special part of our API request that allows us to provide additional context to the data we’re sending.&lt;/p&gt;

&lt;p&gt;This is useful for a number of reasons, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Adding authentication details to prove that we are permitted to take whatever action we’re requesting.&lt;/li&gt;
&lt;li&gt;Storing cookies.&lt;/li&gt;
&lt;li&gt;Configuring whether and how we want our request and response to be cached.&lt;/li&gt;
&lt;li&gt;Giving information about how our request is formatted and the type of response we expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2cNaL_Zw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1685701489/cms/API-integration/API_incident_https___nordicapis.com_20-impressive-api-economy-statistics__lkamoz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2cNaL_Zw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1685701489/cms/API-integration/API_incident_https___nordicapis.com_20-impressive-api-economy-statistics__lkamoz.webp" alt="API authorization stats" title="API authorization stats" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://nordicapis.com/20-impressive-api-economy-statistics/"&gt;NordisCapis&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;We’ve created a dedicated guide to &lt;a href="https://budibase.com/blog/inside-it/api-headers/"&gt;API headers&lt;/a&gt; which explains this in more depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Request bodies
&lt;/h3&gt;

&lt;p&gt;Finally, we have the body of our request. This is where we store any data objects and attributes that we want to send in our request - mostly within PUT and PATCH, and POST requests.&lt;/p&gt;

&lt;p&gt;PUT requests are intended to replace the &lt;em&gt;entire&lt;/em&gt; entity. So, we’d need to provide our new values, as well as all of the existing ones. In this case, the body for Franky’s new favorite meal might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

“lions”:{

​ “lionName”: “Franky”,

​ “age”: 16,

​ “adoptionDate”: 2016-02-02,

​ “favoriteFood”: “Chicken”

​ }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PATCH requests can be used to update &lt;em&gt;only specified fields&lt;/em&gt;. Basically, that means that we don’t need to fill in all of the existing data. If we specify an &lt;em&gt;existing&lt;/em&gt; attribute, its value is modified.&lt;/p&gt;

&lt;p&gt;To update Franky’s favorite meal, we could simply use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{

“lions”:{

​ “favoriteFood”: “Chicken”

​ }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we specify an attribute that &lt;em&gt;doesn’t exist&lt;/em&gt;, then this will be added to the current schema, along with whatever value we give it - assuming we have permissions to edit the schema.&lt;/p&gt;

&lt;p&gt;Whether we’re using PUT or PATCH, we’ll still need the same query parameters to filter the data set and &lt;em&gt;only&lt;/em&gt; update Franky’s row.&lt;/p&gt;

&lt;p&gt;Note that different servers will have rules in the backend that constrain how we take different actions, so read the vendor’s documentation for guidance on how you’re supposed to modify values.&lt;/p&gt;

&lt;h2&gt;
  
  
  API server
&lt;/h2&gt;

&lt;p&gt;Next, we can move on to the server. This is the platform, tool, data set, or other node that we send our request to. &lt;/p&gt;

&lt;p&gt;The first role that this plays is providing an API in the first place. &lt;/p&gt;

&lt;p&gt;So, the vendor specifies what the endpoint is, which objects are exposed to the API, what actions developers can take, how they can do this, and what the application will do in response.&lt;/p&gt;

&lt;p&gt;They should also provide detailed documentation to outline all of this information for developers.&lt;/p&gt;

&lt;p&gt;The server then determines how to handle requests. This includes authentication, data validation, retrieving data, creating responses, caching and cookies, and taking any other actions that are provided for.&lt;/p&gt;

&lt;p&gt;To understand this, we need to examine two distinct layers of whichever applications we’re dealing with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The data layer.&lt;/li&gt;
&lt;li&gt;The business process layer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--htjdJoV2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682672764/cms/on-prem-vs-cloud/Cloud_Spending_httpswww.cloudzero.comblogcloud-computing-statistics_a4evhk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--htjdJoV2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682672764/cms/on-prem-vs-cloud/Cloud_Spending_httpswww.cloudzero.comblogcloud-computing-statistics_a4evhk.webp" alt="components of an api" title="components of an api" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(&lt;a href="https://www.cloudzero.com/blog/cloud-computing-statistics"&gt;CloudZero&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Let’s take each in turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data layer
&lt;/h3&gt;

&lt;p&gt;As the name suggests, the data layer is where data is stored. At the most basic level, this is the data source that we’re querying using our API requests - whichever operation we’re using. So, in the simplest possible example, this would be the target application’s database.&lt;/p&gt;

&lt;p&gt;Therefore, the data layer comprises not just the actual attributes and values stored, but also the underlying data structures and schema of this. &lt;/p&gt;

&lt;p&gt;Check out our post - &lt;a href="https://budibase.com/blog/data/what-is-a-database-schema/"&gt;what is a database schema?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, real-world data layers are often a bit more complex than this.&lt;/p&gt;

&lt;p&gt;There are plenty of ways that the database can enforce rules on the data that we can use in API requests - including validation rules, access control policies, stored procedures, query permissions, and any constraints that are applied to individual attributes.&lt;/p&gt;

&lt;p&gt;For example, some attributes might be read-only, while others might be updatable only by certain users. Or, they might have rules applied to only permit certain values - like if a certain attribute must be unique.&lt;/p&gt;

&lt;p&gt;In the context of sending API requests, how these rules are implemented isn’t necessarily a big concern. We only really care about whether something is possible or not, which should be outlined in the vendor’s docs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Process layer
&lt;/h3&gt;

&lt;p&gt;The process layer is where the target application carries out its &lt;em&gt;own actions&lt;/em&gt; on the data layer. We might be able to implement these processes without substantially altering the underlying data.&lt;/p&gt;

&lt;p&gt;For example, if we had a database that stored all of our sales data - a basic process might be generating and sending an invoice.&lt;/p&gt;

&lt;p&gt;This is important because many of these actions will be &lt;em&gt;triggerable&lt;/em&gt; via API requests.&lt;/p&gt;

&lt;p&gt;For instance, we might have a customer portal that allows users to generate invoices based on their previous orders.&lt;/p&gt;

&lt;p&gt;Depending on how the vendor sets up this process, we might need to use a POST or PUT method in order to make this request successfully.&lt;/p&gt;

&lt;p&gt;We’d also need to provide the right data in our client to select the appropriate data - that is, getting the right data for the current user and whichever time frame they need the invoice for.&lt;/p&gt;

&lt;p&gt;It’s harder to generalize here than it is with basic data management actions. The important thing is to fully understand the vendor’s requirements around how to trigger different actions in the process layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  API response
&lt;/h2&gt;

&lt;p&gt;The next component of an API that we need to get our heads around is the &lt;em&gt;response&lt;/em&gt;. This is what’s sent from the server back to the client after a request is made.&lt;/p&gt;

&lt;p&gt;This provides three key roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Outlining whether or not the request action was successful - and if not, why.&lt;/li&gt;
&lt;li&gt;Providing extra information around authentication, cookies, and caching.&lt;/li&gt;
&lt;li&gt;Passing data back to the client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these has its own distinct section. &lt;/p&gt;

&lt;p&gt;Let’s take a look.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status code
&lt;/h3&gt;

&lt;p&gt;First, we have the status code. This is a three-digit code that indicates what the server has done with our request. The first digit indicates the category of the status, and the second two indicate the specific status.&lt;/p&gt;

&lt;p&gt;Codes in the 200s indicate that the request is successful. 300s relate to redirects. 400s indicate an issue with the request. 500s indicate an issue with the server which prevented the request from being actioned.&lt;/p&gt;

&lt;p&gt;In theory, we could see the full suite of HTTP status codes in response to REST requests.&lt;/p&gt;

&lt;p&gt;In practice though, there are a few key ones that we need to be aware of. The most common status codes include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;200&lt;/strong&gt; - The request was successful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;201&lt;/strong&gt; - The request succeeded and a new data object was created.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;303&lt;/strong&gt; - The response can be found at a different location.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;400&lt;/strong&gt; - The request was bad and the server couldn’t understand it - likely due to a syntax issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;401&lt;/strong&gt; - The response was not authorized.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;404&lt;/strong&gt; - The requested response wasn’t found.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;408&lt;/strong&gt; - Request timeout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;500&lt;/strong&gt;  - Internal server error.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;501&lt;/strong&gt; - The server doesn’t support the requested method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;504&lt;/strong&gt;  - The server is acting as a gateway and timed out trying to retrieve the requested resources.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Response headers
&lt;/h3&gt;

&lt;p&gt;The response can also include header parameters. These work in exactly the same way as they do within an API request - to provide context to the information included in the response itself.&lt;/p&gt;

&lt;p&gt;Most of the time, response headers are used to do some combination of three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sending cookies to the client, to be stored for user management purposes.&lt;/li&gt;
&lt;li&gt;Stipulating which additional authentication information is required.&lt;/li&gt;
&lt;li&gt;Giving information about the format of the response body.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Body
&lt;/h3&gt;

&lt;p&gt;The final component of an API we need to be aware of is the response body. Just like the request body, this is used to hold the data that we want to pass to the client. However, this is only required in responses to certain request methods.&lt;/p&gt;

&lt;p&gt;So, if we’re sending a DELETE, PUT, PATCH, or POST method, we don’t necessarily need the server to send any data back. We’d simply get a 200 or 201 status code to indicate that the request was successfully actioned.&lt;/p&gt;

&lt;p&gt;But, we always need a response body in a GET request, in order to pass the data that was retrieved back to the client.&lt;/p&gt;

&lt;p&gt;Just like earlier, this comes in the form of a data object - a series of key/value pairs representing the information that was requested.&lt;/p&gt;

&lt;p&gt;And that’s it - all of the components of an API that we need to be aware of.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 types of APIs
&lt;/h2&gt;

&lt;p&gt;For a bit of extra context, it’s important to know that APIs aren’t all created equal. In fact, there are a couple of different types of APIs that vendors might provide for different kinds of developers and projects.&lt;/p&gt;

&lt;p&gt;These are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private APIs&lt;/strong&gt; - Internal APIs that are created to be used by a company’s own developers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public APIs&lt;/strong&gt; - APIs that are publicly available and usable by anyone - although these might require payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partner APIs&lt;/strong&gt; - APIs that are made to satisfy particular business relationships. For example native integrations with other platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composite APIs&lt;/strong&gt; - A combination of multiple APIs that are used to action related or interdependent tasks. Usually, the idea is to improve performance compared to using multiple individual API requests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MFHOGf7O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1689339019/cms/what-are-the-components-of-an-api/Budibase_Screenshot_nypfwr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MFHOGf7O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1689339019/cms/what-are-the-components-of-an-api/Budibase_Screenshot_nypfwr.webp" alt="API parameters" title="API parameters" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check out our ultimate guide to &lt;a href="https://budibase.com/blog/inside-it/it-transformation/"&gt;IT transformation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manage internal processes with Budibase
&lt;/h2&gt;

&lt;p&gt;Budibsae is the ideal solution for building sleek, integrated applications that leverage all kinds of existing data assets. We’ve even got our own public API, alongside full support for REST data sources and automation actions.&lt;/p&gt;

&lt;p&gt;Here’s what makes Budibase tick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our open-source, low-code platform
&lt;/h3&gt;

&lt;p&gt;Budibase is the fast, cost-effective way to build professional applications. We offer extensive data support, market-leading extensibility, autogenerated screens, optional self-hosting, intuitive design tools, flexible automations, and much more.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://budibase.com/product"&gt;features overview&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect your data
&lt;/h3&gt;

&lt;p&gt;We’re proud to lead the pack for external data support. Budibase offers dedicated data connectors for MSSQL, MySQL, Postgres, Oracle, S3, Redis, Mongo, Couch, Arango, Google Sheets, REST API, and more.&lt;/p&gt;

&lt;p&gt;We’ve also got our own built-in database, alongside full support for custom data sources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional self-hosting
&lt;/h3&gt;

&lt;p&gt;Security-first organizations love Budibase for the power to deploy custom solutions to their own infrastructure. We offer self-hosting through Kubernetes, Docker, Docker Compose, Digital Ocean, Linode, Portainer, and many more.&lt;/p&gt;

&lt;p&gt;Or, choose Budibase Cloud and let us handle everything. Check out our &lt;a href="https://budibase.com/pricing/"&gt;pricing page&lt;/a&gt; to learn more about both options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Public API
&lt;/h3&gt;

&lt;p&gt;Budibase has its own public API for all kinds of application management, data manipulation, integration, and customization tasks.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://docs.budibase.com/docs/public-api"&gt;API docs&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automations and integrations
&lt;/h3&gt;

&lt;p&gt;Build custom automation rules with minimal custom code. Combine, nest, and configure our built-in triggers and actions to build, test, and publish powerful flows in minutes.&lt;/p&gt;

&lt;p&gt;We also offer extensive integration with third-party tools, using Zapier, REST API, and WebHooks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role-based access control
&lt;/h3&gt;

&lt;p&gt;Budibase offers configurable role-based access control. Assign users to defined roles and grant permissions based on data sources, queries, automation rules, screen UIs, or individual components.&lt;/p&gt;

&lt;p&gt;We also provide free SSO through OAuth, OpenID, Microsoft, and more - alongside secure environment variables for storing database credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom plug-ins
&lt;/h3&gt;

&lt;p&gt;Budibase won’t be beaten for extensibility. Use our dedicated CLI to build custom data sources, automation actions, and components. Or, import community contributions from GitHub, in just a couple of clicks.&lt;/p&gt;

&lt;p&gt;Take a look at our &lt;a href="https://docs.budibase.com/docs/custom-plugin"&gt;plug-ins docs&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  50+ free app templates
&lt;/h3&gt;

&lt;p&gt;Companies around the world - in all kinds of industries - choose Budibase to build secure, performant apps in a fraction of the time. To show off what’s possible, we’ve built more than fifty free, totally customizable &lt;a href="https://budibase.com/templates"&gt;app templates&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sign up for Budibase today, &lt;em&gt;for free&lt;/em&gt;, to start building custom tools the fast, easy way.&lt;/p&gt;

</description>
      <category>beginners</category>
    </item>
    <item>
      <title>Build an Inventory Management App for Free [VIDEO]</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Wed, 28 Jun 2023 10:04:24 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/build-an-inventory-management-app-for-free-video-2e02</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/build-an-inventory-management-app-for-free-video-2e02</guid>
      <description>&lt;p&gt;Keeping track of stock is one of the most fundamental processes in any customer-facing business. Today, we’re checking out how to build a custom inventory management app for &lt;em&gt;free&lt;/em&gt; - in just 20 minutes.&lt;/p&gt;

&lt;p&gt;Even better, since we’re going to use Budibase to build it, you’ll barely need any coding skills - and we’ll have a fully functional application before your coffee gets cold.&lt;/p&gt;

&lt;p&gt;All we need to do is connect our data, add a couple of custom queries, and start using Budibase’s built-in components to create a professional UI for managing stock levels.&lt;/p&gt;

&lt;p&gt;Let’s jump right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we building?
&lt;/h2&gt;

&lt;p&gt;Our inventory management tool is going to be a one-screen app, but don’t let this fool you. It’s actually going to pack quite a punch. &lt;/p&gt;

&lt;p&gt;In terms of requirements, we basically want to enable our end users to do five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browse products based on their category.&lt;/li&gt;
&lt;li&gt;Edit product information.&lt;/li&gt;
&lt;li&gt;Search for items based on their name.&lt;/li&gt;
&lt;li&gt;Check which items are low on stock.&lt;/li&gt;
&lt;li&gt;Reorder stock where necessary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under the hood, we’re querying three tables from an existing MySQL database. &lt;/p&gt;

&lt;p&gt;Along with Budibase’s native &lt;em&gt;read&lt;/em&gt; functionality, we’ll also need three custom queries.&lt;/p&gt;

&lt;p&gt;But don’t worry if you’re not a SQL whizz - we’ll provide all of the code you need below and show you exactly how to use it.&lt;/p&gt;

&lt;p&gt;Without further ado…&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/5p4Vwv1xQl0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a free inventory management app in 7 steps
&lt;/h2&gt;

&lt;p&gt;If you haven’t already, you’ll need to sign up for a Budibase account. Our pricing structure is the most generous in the low-code space, and you can do everything you need to build and deploy a fully-functioning inventory management tool with our free tier.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a new app
&lt;/h3&gt;

&lt;p&gt;We’ll start by hitting &lt;em&gt;create new app&lt;/em&gt; on the Budibase portal. Then press &lt;em&gt;start from scratch&lt;/em&gt;. You’ll be prompted to choose a name and URL for your new app project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XlxK5ZOy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_1_g9ypuk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XlxK5ZOy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_1_g9ypuk.webp" alt="Inventory Management App Free" title="Inventory Managemet App Free" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, we’ll be asked to select our data source. Budibase offers a market-leading range of external data connectors, as well as our own built-in database.&lt;/p&gt;

&lt;p&gt;We’re going to choose MySQL, but you can go with whatever option matches up with where your inventory data lives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fOZpR3jB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_2_dp8nz6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fOZpR3jB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_2_dp8nz6.webp" alt="Choose a data source" title="Choose a data source" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Connect your data
&lt;/h3&gt;

&lt;p&gt;Now, we need to add our database credentials, including the &lt;em&gt;host&lt;/em&gt;, &lt;em&gt;port&lt;/em&gt;, &lt;em&gt;database,&lt;/em&gt; and our &lt;em&gt;login details&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WwKfb_vG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_3_udq7nh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WwKfb_vG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_3_udq7nh.webp" alt="Connect to MySQL" title="Connect to MySQL" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we’ve done this, we can use Budibase’s &lt;em&gt;fetch tables&lt;/em&gt; functionality to access our tables from the &lt;em&gt;data&lt;/em&gt; tab of the builder.&lt;/p&gt;

&lt;p&gt;Once we’ve done this, the tables will look like this in the back end:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TCf_wHCh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_App_4_llpjir.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TCf_wHCh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_App_4_llpjir.webp" alt="Inventory Management Database" title="Inventory Management Database" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Already, we can edit entries without writing queries, using our spreadsheet-like interface.&lt;/p&gt;

&lt;p&gt;However, we’ll need some slightly more advanced logic in our data layer to achieve our desired functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Add custom queries
&lt;/h3&gt;

&lt;p&gt;This means adding some custom queries. If you’re not familiar with SQL, basically a query is an instruction to retrieve information from our database in a particular format - perhaps by applying some transformations or filters.&lt;/p&gt;

&lt;p&gt;Remember, our three tables are &lt;em&gt;products, inventories&lt;/em&gt;, and &lt;em&gt;reorders&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We also need three custom queries to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Return a list of our product categories, without any duplicates.&lt;/li&gt;
&lt;li&gt;Return our product information from the &lt;em&gt;products&lt;/em&gt; table, along with their respective stock levels from the &lt;em&gt;inventories&lt;/em&gt; table.&lt;/li&gt;
&lt;li&gt;Return the total number of items that have been &lt;em&gt;reordered&lt;/em&gt; for each product entry.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From our MySQL configuration page, we’ll start by hitting &lt;em&gt;add query&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tSn8HnK0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_App_5_btn0dh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tSn8HnK0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_App_5_btn0dh.webp" alt="Fetch Tables" title="Fetch Tables" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This presents us with the following options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Query name&lt;/strong&gt; - what we’ll call our query within Budibase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function&lt;/strong&gt; - the type of action we’re performing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access level&lt;/strong&gt; - which user roles can use our query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fields&lt;/strong&gt; - where we’ll input our SQL query itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transformer&lt;/strong&gt; - any JavaScript we want to apply to the data returned by our query before we pass it to Budibase’s front-end or automation builder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C0mIv-fr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525690/cms/inventory-management-app/Inventory_Management_6_km7a5s.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C0mIv-fr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525690/cms/inventory-management-app/Inventory_Management_6_km7a5s.webp" alt="Custom Queries" title="Custom Queries" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All of our queries use the &lt;em&gt;read&lt;/em&gt; function. Other than that, all we need to worry about is giving our query a name and pasting the SQL that we’ll provide in a second.&lt;/p&gt;

&lt;p&gt;So first, we want to return a list of our product categories. We’ll use a SELECT statement, with a GROUP BY clause to achieve this. Specifically:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SELECT category FROM Products GROUP BY category;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This returns all of the &lt;em&gt;category&lt;/em&gt; values from our &lt;em&gt;Products&lt;/em&gt; table, but groups all the duplicates together, returning:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9XLaZ9wU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_7_dczepg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9XLaZ9wU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_7_dczepg.webp" alt="Result" title="Result" width="669" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we have a query that returns the details of each row from the &lt;em&gt;products table&lt;/em&gt;, along with the stock level from their respective entries in the &lt;em&gt;inventories&lt;/em&gt; table.&lt;/p&gt;

&lt;p&gt;We’ll use the following query:&lt;/p&gt;

&lt;p&gt;`SELECT SUM(Quantity) as TotalQuantity, &lt;/p&gt;

&lt;p&gt;Inventory.ProductID as ProductID,&lt;/p&gt;

&lt;p&gt;Products.ProductName as ProductName,&lt;/p&gt;

&lt;p&gt;​ Products.Description as Description,&lt;/p&gt;

&lt;p&gt;​ Products.Image as Image,&lt;/p&gt;

&lt;p&gt;​ Products.Category as Category&lt;/p&gt;

&lt;p&gt;FROM Inventory&lt;/p&gt;

&lt;p&gt;JOIN Products ON Inventory.ProductID = Products.ProductID&lt;/p&gt;

&lt;p&gt;GROUP BY Inventory.ProductID`&lt;/p&gt;

&lt;p&gt;This looks a little bit more complex, but really our query does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tells SQL to retrieve the product details from our &lt;em&gt;products&lt;/em&gt; table and the quantity from the &lt;em&gt;inventories&lt;/em&gt; table, and gives the returned values slightly more concise names.&lt;/li&gt;
&lt;li&gt;States that entries in the &lt;em&gt;products&lt;/em&gt; and &lt;em&gt;inventories&lt;/em&gt; tables with the same &lt;em&gt;ProductID&lt;/em&gt; attribute are the same item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Giving us:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9Artfq-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_8_f1q0b1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9Artfq-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_8_f1q0b1.webp" alt="Inventory Management App Free" title="Inventory Management App Free" width="687" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we want to query our &lt;em&gt;reorders&lt;/em&gt; table, aggregating all of the quantities for each product. So - if we two users had reordered 10 Welding Masks each, the total quantity returned would be 20.&lt;/p&gt;

&lt;p&gt;We’ll use this query:&lt;/p&gt;

&lt;p&gt;`SELECT ProductID, SUM(Quantity) as TotalQuantity&lt;/p&gt;

&lt;p&gt;FROM Reorders&lt;/p&gt;

&lt;p&gt;WHERE Status = 'Requested'&lt;/p&gt;

&lt;p&gt;GROUP BY ProductID&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;This returns:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9LuWvF---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_9_n2rzzy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9LuWvF---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_9_n2rzzy.webp" alt="Query Response" title="Query Response" width="682" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s all of our custom queries in place. We’ll return to these later when it comes time to actually use them.&lt;/p&gt;

&lt;p&gt;For now, we can start building our free inventory management app’s UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Build a category list
&lt;/h3&gt;

&lt;p&gt;The bulk of our UI will be based around clickable cards for each individual product - sorted into different categories. &lt;/p&gt;

&lt;p&gt;We’ll start by adding a &lt;em&gt;repeater block&lt;/em&gt; which we’ll set to our &lt;em&gt;categoryList&lt;/em&gt; custom query. Inside this, we’ll place a &lt;em&gt;cards block&lt;/em&gt; named &lt;em&gt;ProductBlock&lt;/em&gt; with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title -&lt;/strong&gt; Category List.CategoryList.category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data&lt;/strong&gt; - Products&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cards Titles -&lt;/strong&gt; ProductBlock.Products.ProductName&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtitle&lt;/strong&gt; - ProductBlock.Products.Category&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt; - ProductBlock.Products.Description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image&lt;/strong&gt; - ProductBlock.Products.Image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve also added a filter expression so that the &lt;em&gt;Category&lt;/em&gt; attribute for each card needs to match the current &lt;em&gt;category&lt;/em&gt; from our repeater block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9bHmTzCg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_10_bfc5cv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9bHmTzCg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_10_bfc5cv.webp" alt="Inventory Management App Free" title="Inventory Management App Free" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the whole thing should look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nfsepu7y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_11_m1soa6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nfsepu7y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_11_m1soa6.webp" alt="Cards Repeater" title="Cards Repeater" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Create a side panel
&lt;/h3&gt;

&lt;p&gt;Now, we want users to be able to interact with the data we’re displaying here. Specifically, they need to be able to update entries in our &lt;em&gt;products&lt;/em&gt; database. We want to stick to a one-screen design though.&lt;/p&gt;

&lt;p&gt;So, the best option is adding a side panel form that pops up when we click on one of our cards.&lt;/p&gt;

&lt;p&gt;We’ll start by adding a &lt;em&gt;side panel&lt;/em&gt; component - which we’ll name &lt;em&gt;ProductDetails&lt;/em&gt;. This is basically a design container that only shows up when we tell it to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8Tojtgrl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525690/cms/inventory-management-app/Inventory_Management_12_iysxxz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8Tojtgrl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525690/cms/inventory-management-app/Inventory_Management_12_iysxxz.webp" alt="Side Panel" title="Side panel" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So next, we need to set up a trigger for our side panel to appear. This is going to be when someone clicks on a card. So, we’ll return to our cards block and hit &lt;em&gt;define actions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And we can add the &lt;em&gt;Open Side Panel&lt;/em&gt; action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BdaV7nyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_13_ycsrbj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BdaV7nyo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_13_ycsrbj.webp" alt="Open Side Panel" title="Open Side Panel" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While we’re here, we need to add another action to set a &lt;em&gt;state&lt;/em&gt;. Essentially, we’re going to place an &lt;em&gt;update&lt;/em&gt; form in our side panel, so we need to tell Budibase to load the data for the row that we’ve clicked on.&lt;/p&gt;

&lt;p&gt;To do this, we’ll first add another on-click action before our side panel opens. This time we’re choosing &lt;em&gt;update state&lt;/em&gt;. This lets us save a variable in the background. We’ll call it &lt;em&gt;current-product&lt;/em&gt; and set it equal to ProductBlock.Products.ProductID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5I07sT0n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_14_bk6ae3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5I07sT0n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_14_bk6ae3.webp" alt="State variables" title="Set state" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now when we click on a card, Budibase saves the relevant ProductID and then opens our side panel.&lt;/p&gt;

&lt;p&gt;We’ll start by adding a &lt;em&gt;form block&lt;/em&gt; nested within our side panel component. We need to set the &lt;em&gt;Type&lt;/em&gt; to &lt;em&gt;Update&lt;/em&gt; and then set the &lt;em&gt;Row ID&lt;/em&gt; to State.current-product. We’ll also give it a name so that it’s easy to identify in our component tree.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--84MPnjX2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_15_zobn0o.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--84MPnjX2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525691/cms/inventory-management-app/Inventory_Management_15_zobn0o.webp" alt="Update Form" title="Update form" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Already, we have a fully functional update form. But, it could look a little bit snazzier for sure. &lt;/p&gt;

&lt;p&gt;Let’s start by adding a new data provider to pull the information for our product. Inside, we’ll add a &lt;em&gt;headline&lt;/em&gt; component and an image. The headline has its &lt;em&gt;Text&lt;/em&gt; field set to:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ProductDetailProvider.Rows.0.ProductName ( ProductDetailProvider.Rows.0.Category)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And the image has its &lt;em&gt;URL&lt;/em&gt; as ProductDetailProvider.Rows.0.Image. The whole thing looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--USp3UhR0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525693/cms/inventory-management-app/Inventory_Management_16_fgwdxq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--USp3UhR0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525693/cms/inventory-management-app/Inventory_Management_16_fgwdxq.webp" alt="Inventory Management UX" title="inventory management UX" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll make one more small change to this later, but for now, that’s our side panel UI complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Add a search bar
&lt;/h3&gt;

&lt;p&gt;The next thing we need to build is our product search functionality. The way this works is pretty simple. We’ll provide users with a text field where they can enter the name of the product they’re looking for.&lt;/p&gt;

&lt;p&gt;If they do this, we’ll filter our cards block to show any products that match this.&lt;/p&gt;

&lt;p&gt;So, we’ll start by adding the following new components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A container&lt;/strong&gt; - to hold everything in a neat fashion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A headline&lt;/strong&gt; - purely for UX.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A form&lt;/strong&gt; - all form components need to be nested in a form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A text field&lt;/strong&gt; - where users can enter their desired product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Altogether, this will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--otHrKzN_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_17_fljgbt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--otHrKzN_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525692/cms/inventory-management-app/Inventory_Management_17_fljgbt.webp" alt="Search Bar" title="Search Bar" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll also set an action on our text field to save the value that users enter. So, we’ll use the &lt;em&gt;Update State&lt;/em&gt; action to record whatever they type in with the key &lt;em&gt;search-value&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jdQlxh8b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_18_fr0gq1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jdQlxh8b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_18_fr0gq1.webp" alt="user defined filtering" title="user defined filtering" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But, nothing will happen when users type in a product name. For this, we’ll need to update our filtering expression on our cards block from earlier.&lt;/p&gt;

&lt;p&gt;Under &lt;em&gt;define filters&lt;/em&gt; we’ll add a second expression. This time, we want to display any products where the &lt;em&gt;ProductName&lt;/em&gt; attribute is &lt;em&gt;like&lt;/em&gt; our &lt;em&gt;search-value&lt;/em&gt; state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UiYlY_35--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_19_mfmowr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UiYlY_35--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525689/cms/inventory-management-app/Inventory_Management_19_mfmowr.webp" alt="Inventory Management App Free" title="Inventory Management App Free" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we have fully working search functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Low-stock notifications
&lt;/h3&gt;

&lt;p&gt;The last thing we want to build is our low-stock notifications. Remember, we wrote a custom query earlier that’s going to do most of the legwork for us here. We’ll add another container to keep things tidy.&lt;/p&gt;

&lt;p&gt;Inside this, we’ll place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A data provider set to our LowStockQuery.&lt;/li&gt;
&lt;li&gt;A headline.&lt;/li&gt;
&lt;li&gt;A repeater to access the values from our LowStockQuery provider.&lt;/li&gt;
&lt;li&gt;A form, so that we can update our &lt;em&gt;reorders&lt;/em&gt; data with user actions.&lt;/li&gt;
&lt;li&gt;A second data provider, set to our custom Reorder query.&lt;/li&gt;
&lt;li&gt;A cards block.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The finished product will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Unhb1pYy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525693/cms/inventory-management-app/Inventory_Management_20_j0hp9k.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Unhb1pYy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525693/cms/inventory-management-app/Inventory_Management_20_j0hp9k.webp" alt="Stock Management" title="Stock management" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The card component also has a button, with two actions. First, it adds a new row to the &lt;em&gt;reorders&lt;/em&gt; table, with the &lt;em&gt;productID&lt;/em&gt; set to the relevant data from the card, and the &lt;em&gt;quantity&lt;/em&gt; set to &lt;em&gt;10&lt;/em&gt;. We then set the button text to &lt;em&gt;order 10 more&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AstnI8PB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525690/cms/inventory-management-app/Inventory_Management_21_ikqueg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AstnI8PB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687525690/cms/inventory-management-app/Inventory_Management_21_ikqueg.webp" alt="Button Actions" title="Button Actions" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, users can directly order new stock, but they can also see if someone else has already done so.&lt;/p&gt;

&lt;p&gt;And that’s our free inventory management app completed. We could tweak the design a little bit more if we wanted to, but otherwise, we’re ready to publish and send our tool to users.&lt;/p&gt;

&lt;p&gt;You can also check out our guide to building a &lt;a href="https://budibase.com/blog/tutorials/sql-gui/"&gt;SQL GUI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn data into action with Budibase
&lt;/h2&gt;

&lt;p&gt;At Budbase, we’re on a mission to help teams turn data into action. Businesses around the world choose our open-source, low-code platform to build all kinds of custom web apps, at pace.&lt;/p&gt;

&lt;p&gt;Here’s what makes Budibase tick.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our open-source, low-code platform
&lt;/h3&gt;

&lt;p&gt;Our design ethos is &lt;em&gt;simplicity by default; extensibility when you need it.&lt;/em&gt; Our platform offers intuitive design tools, extensive customization, market-leading data support, autogenerated UIs, and much more.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://budibase.com/product"&gt;features overview&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  External data support
&lt;/h3&gt;

&lt;p&gt;Budibase leads the pack for external data support. We offer dedicated connectors for MySQL, SQL Server, Postgres, Airtable, S3, Mongo, Arango, Oracle, Couch, Redis, REST API, Google Sheets, and more.&lt;/p&gt;

&lt;p&gt;We’ve even got our own built-in database to help get you up and running in seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional self-hosting
&lt;/h3&gt;

&lt;p&gt;Security-conscious organizations love Budibase for the power to deploy their tools however they choose. We offer optional self-hosting through Kubernetes, Docker, Digital Ocean, Portainer, and more.&lt;/p&gt;

&lt;p&gt;Or, use Budibase Cloud and let us handle everything. Check out our &lt;a href="https://budibase.com/pricing"&gt;pricing page&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automations &amp;amp; integrations
&lt;/h3&gt;

&lt;p&gt;Budibase makes it a breeze to automate all kinds of workflows and processes. Use our flow-based editor to combine built-in triggers and actions to create your perfect automations, with minimal coding skills.&lt;/p&gt;

&lt;p&gt;We also offer extensive third-party app integrations, using webhooks, Zapier, REST API, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role-based access control
&lt;/h3&gt;

&lt;p&gt;Use our native role-based access control to tailor users’ data exposure without compromising on experiences. Add users to defined roles and grant permissions based on data sources, queries, automations, app screens, or individual components.&lt;/p&gt;

&lt;p&gt;We also offer free SSO through OpenID, OAuth, Microsoft, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom plug-ins
&lt;/h3&gt;

&lt;p&gt;Budibase is the clear leader for extensibility in the low-code space. Build your own components, data sources, and automation blocks with our dedicated CLI tools - or import community contributions from GitHub at the click of a button.&lt;/p&gt;

&lt;p&gt;Check out our custom &lt;a href="https://docs.budibase.com/docs/custom-plugin"&gt;plug-ins documentation&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  50+ free application templates
&lt;/h3&gt;

&lt;p&gt;Our users choose Budibase to build all kinds of internal tools, web apps, and utilities, in minutes, not months. We’ve even built more than fifty free, customizable &lt;a href="https://budibase.com/templates"&gt;app templates&lt;/a&gt; to help get you started.&lt;/p&gt;

&lt;p&gt;Sign up to Budibase today to start building professional applications for free.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>beginners</category>
      <category>lowcode</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Build MySQL Admin Tools in 6 Steps [VIDEO]</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Tue, 27 Jun 2023 12:41:07 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/how-to-build-mysql-admin-tools-in-6-steps-video-4p0c</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/how-to-build-mysql-admin-tools-in-6-steps-video-4p0c</guid>
      <description>&lt;p&gt;MySQL admin tools are some of the simplest applications around - but they can play a vital role in all kinds of daily workflows. &lt;/p&gt;

&lt;p&gt;The unfortunate reality is that the kinds of colleagues who are responsible for most admin tasks don’t have the technical expertise or time to sit around writing custom queries all day. Therefore, we often need custom solutions to bridge this gap.&lt;/p&gt;

&lt;p&gt;IT teams are tasked with outputting huge numbers of internal tools for managing data sets. These aren’t the most exciting solutions in the world, but they do take up a massive amount of internal development resources.&lt;/p&gt;

&lt;p&gt;Today, we’re going to check out how we can use Budibase to produce admin tools for MySQL in a matter of minutes - not days.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/IyMmw6jENeo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we building?
&lt;/h2&gt;

&lt;p&gt;We want to build a tool that’s an accurate representation of the kinds of tasks your average admin colleague will need to perform on a daily basis. So, we’re going for the most basic solution of them all - a simple &lt;em&gt;CRUD app.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We’re going to base this around some dummy data for inventory management - just so we’re dealing with a use case that most of you will be familiar with.&lt;/p&gt;

&lt;p&gt;Essentially, we have a MySQL database with five tables - and we’re going to create a simple tool for users to &lt;em&gt;create, read, update&lt;/em&gt;, and &lt;em&gt;delete&lt;/em&gt; entries - including managing relationships between different entities.&lt;/p&gt;

&lt;p&gt;You might also like our tutorial on how to build a &lt;a href="http://budibase.com/blog/tutorials/mysql-gui"&gt;MySQL GUI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s jump right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build MySQL admin tools
&lt;/h2&gt;

&lt;p&gt;We’re going to use Budibase to build our MySQL admin tools without writing a single line of code.&lt;/p&gt;

&lt;p&gt;The whole thing should take us about fifteen to twenty minutes - but there’s also plenty of scope for you to put your own spin on the build.&lt;/p&gt;

&lt;p&gt;Here’s how.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create a new application
&lt;/h3&gt;

&lt;p&gt;First, we want to create a new application project. From your Budibase portal, hit &lt;em&gt;create new app&lt;/em&gt; and choose &lt;em&gt;start from scratch&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--urcRaOaI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_1_htvobg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--urcRaOaI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_1_htvobg.webp" alt="Create a Budibase app" title="Create a Budibase app" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’re then prompted to give our app a name. Budibase uses this to generate a URL extension based on your tenant address - but you can override this and specify whatever slug you like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pzq_9a81--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_2_hvvbwb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pzq_9a81--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_2_hvvbwb.webp" alt="MySQL Admin Tools" title="MySQL Admin Tools" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, we’re asked to choose a database. As you might guess, we’re picking MySQL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OdWU3MIH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_3_yeoihl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OdWU3MIH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_3_yeoihl.webp" alt="Choose a data source" title="Choose a data source" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Connecting our data
&lt;/h3&gt;

&lt;p&gt;On the next screen, we’re prompted to enter our database credentials. Once we’ve done this, we can hit &lt;em&gt;connect&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6PXgm7y8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_4_whfg6g.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6PXgm7y8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_4_whfg6g.webp" alt="SQL credentials" title="SQL credentials" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we fetch our tables, we can view and manipulate our MySQL database tables within Budibase’s &lt;em&gt;Data&lt;/em&gt; section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--almG30ge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_5_yeonhy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--almG30ge--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524828/cms/mysql-admin-tools/MySQL_Admin_5_yeonhy.webp" alt="MySQL Admin Tools" title="MySQL Admin Tools" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll make a couple of changes here a little bit later, but for now, that’s our data layer pretty much completed.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Autogenerate CRUD screens
&lt;/h3&gt;

&lt;p&gt;Next, we can start building our MySQL admin tool’s interface. Remember, the &lt;em&gt;requirements&lt;/em&gt; here are that we want to give non-technical users an easy way to carry out CRUD actions on our five MySQL tables.&lt;/p&gt;

&lt;p&gt;Luckily, in Budibase, we can basically create these with a couple of clicks.&lt;/p&gt;

&lt;p&gt;From the design tab, we’re going to hit &lt;em&gt;add screen&lt;/em&gt; and then select &lt;em&gt;list view&lt;/em&gt; on the modal that pops up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HjRBa063--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_6_fhxk6z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HjRBa063--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_6_fhxk6z.webp" alt="New Screens" title="new screens" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we’re prompted to choose which tables we want to add CRUD screens for. We’re going to select all five of the tables in our MySQL database, but we could opt for any combination of these if we wanted to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KTFjc7hy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_7_lfawes.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KTFjc7hy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_7_lfawes.webp" alt="MySQL tables" title="MySQL tables" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we’ve got five admin screens where users can &lt;em&gt;read, update&lt;/em&gt;, or &lt;em&gt;create&lt;/em&gt; entries on each of our tables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ii0KbqTU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_8_f7vooy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ii0KbqTU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_8_f7vooy.webp" alt="CRUD screen" title="CRUD sceren" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To finish these off, we’ll open the component tree on each page, select the relevant table block, and scroll down to check &lt;em&gt;show delete&lt;/em&gt; - giving users the option to delete entries when they click through to edit a row.&lt;/p&gt;

&lt;p&gt;And that’s our basic CRUD screens completed. We’ll return to these in a bit to tidy up the UI slightly, but already, we have fully functioning MySQL admin tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Define relationships
&lt;/h3&gt;

&lt;p&gt;Now, the problem with our current app is that we haven’t accounted for how our five tables relate to each other. That’s a pretty big oversight - since manipulating &lt;em&gt;relational&lt;/em&gt; data is basically the main selling point of SQL.&lt;/p&gt;

&lt;p&gt;Let’s head back to the &lt;em&gt;data&lt;/em&gt; section of the Budibase builder. &lt;/p&gt;

&lt;p&gt;On each table, we have the option to &lt;em&gt;define relationships&lt;/em&gt; - effectively telling Budibase how each of our tables links up with the others.&lt;/p&gt;

&lt;p&gt;For example, each row in our &lt;em&gt;products&lt;/em&gt; table might relate to many rows in our &lt;em&gt;inventories&lt;/em&gt; table. We can hit &lt;em&gt;define relationships&lt;/em&gt; and configure this information, including which variables to use as our primary and foreign keys.&lt;/p&gt;

&lt;p&gt;So, for the relationship between products and inventories, the configuration would look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RuBRvVOv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_10_uy5qy6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RuBRvVOv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_10_uy5qy6.webp" alt="MySQL admin tools" title="MySQL admin tools" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Budibase creates a new relationship column on each table to reflect this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JH7S6DwD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524827/cms/mysql-admin-tools/MySQL_Admin_11_zhd1n4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JH7S6DwD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524827/cms/mysql-admin-tools/MySQL_Admin_11_zhd1n4.webp" alt="Table" title="Tables" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we can repeat this process for all of the other relationships that we need to create between our tables.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Customize our CRUD screens
&lt;/h3&gt;

&lt;p&gt;Now, if we return to our CRUD screens, they’re gotten a bit messier - since we’ve added extra columns to our tables.&lt;/p&gt;

&lt;p&gt;Even worse, from an end-user perspective, some of these even appear to be effectively duplicated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f7SuMk21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524827/cms/mysql-admin-tools/MySQL_Admin_12_unzclq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f7SuMk21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524827/cms/mysql-admin-tools/MySQL_Admin_12_unzclq.webp" alt="UI" title="UI" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can go back to our table block component and configure which columns we actually want to display to users. Since we have our new relationship variables, we no longer strictly need ProductID or WarehouseID, so let’s remove them.&lt;/p&gt;

&lt;p&gt;First, we’ll hit &lt;em&gt;configure columns&lt;/em&gt; and then choose &lt;em&gt;add all columns&lt;/em&gt; in the drawer that opens - then we can use the delete icon to remove any columns we don’t want to display.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CvKTuCCR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_13_numvym.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CvKTuCCR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524826/cms/mysql-admin-tools/MySQL_Admin_13_numvym.webp" alt="Bindings" title="Bindings" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This leaves us with a much neater interface, free from any clutter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JTKJQfc9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_14_dmtlh0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JTKJQfc9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_14_dmtlh0.webp" alt="Table" title="Table" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, we can repeat this step for each of the tables we want to manage with our MySQL admin tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Navigation and design
&lt;/h3&gt;

&lt;p&gt;Finally, we can play around with our app’s overall design to make it a little bit more appealing. By default, every Budibase app has a blank home screen. But we’re not using this, so we can simply remove it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i5tS80lR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_15_e2mtho.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i5tS80lR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_15_e2mtho.webp" alt="MySQL admin tools" title="MySQL admin tools" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to assign one of our remaining screens as our MySQL admin tool’s homepage. We’ll go with inventory, so select that page and check the &lt;em&gt;set as home screen&lt;/em&gt; box in the top right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RTxEMjs7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_16_ouhsvy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RTxEMjs7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_16_ouhsvy.webp" alt="Set home screen" title="Set Home Screen" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also head over to the &lt;em&gt;theme&lt;/em&gt; panel and select one of Budibase’s built-in color palettes, and alter the design of interactive elements.&lt;/p&gt;

&lt;p&gt;The last thing we want to do is remove &lt;em&gt;Home&lt;/em&gt; from our navigation menu - since we don’t have a dedicated home screen anymore. Under navigation hit &lt;em&gt;configure links&lt;/em&gt; and the drawer will allow you to edit your menu.&lt;/p&gt;

&lt;p&gt;Once again, we’ll use the delete icon to remove the &lt;em&gt;Home&lt;/em&gt; option. We can also change the copy our the order of our remaining menu items.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o1RLpiCv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_18_wz3ac6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o1RLpiCv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1687524825/cms/mysql-admin-tools/MySQL_Admin_18_wz3ac6.webp" alt="MySQL Admin TOols" title="MySQL Admin Tools" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s our MySQL admin tools totally finished.&lt;/p&gt;

&lt;p&gt;All that’s left is to hit &lt;em&gt;Publish&lt;/em&gt; and start sending our app to users.&lt;/p&gt;

&lt;p&gt;If you liked this tutorial, check out our guide on how to build a &lt;a href="http://budibase.com/blog/tutorials/sql-gui"&gt;SQL GUI.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn data into action with Budibase
&lt;/h2&gt;

&lt;p&gt;Of course, this is just a basic example of what you &lt;em&gt;could&lt;/em&gt; build with Budibase. To get a flavor of what else is possible, let’s take a look at what Budibase brings to the table for busy IT teams.&lt;/p&gt;

&lt;p&gt;Here’s why Budibase is the ideal solution for all sorts of apps - not just MySQL admin tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our open-source, low-code platform
&lt;/h3&gt;

&lt;p&gt;Our design philosophy is &lt;em&gt;simplicity by default; extensibility when you need it&lt;/em&gt;. Budibase offers a generous free tier, market-leading data support, intuitive design tools, autogenerated UIs, custom automations, and much more.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://budibase.com/product"&gt;features overview&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  External data support
&lt;/h3&gt;

&lt;p&gt;No other low-code tool comes close for data support. We offer dedicated connectors for MySQL, MSSQL, Postgres, Oracle, S3, Airtable, Mongo, Couch, Arango, Google Sheets, REST API, and more.&lt;/p&gt;

&lt;p&gt;We’ve also got our own built-in database, with full support for CSV uploads.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional self-hosting
&lt;/h3&gt;

&lt;p&gt;At Budibase, we empower our users to deploy their solutions, where they want, how they want. We offer self-hosting with Docker, Digital Ocean, Kubernetes, Portainer, Linode, and more.&lt;/p&gt;

&lt;p&gt;We’ve also got our own proprietary cloud hosting option. Check out our &lt;a href="https://budibase.com/pricing"&gt;pricing page&lt;/a&gt; to learn more about both.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automations and integrations
&lt;/h3&gt;

&lt;p&gt;Budibase makes building custom automations a breeze. Define triggers and combine, nest, and configure pre-built automation blocks, with minimal custom code.&lt;/p&gt;

&lt;p&gt;We also offer a huge array of integrations with Zapier, Webhooks, REST, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Role-based access control
&lt;/h3&gt;

&lt;p&gt;Use Budibase’s built-in RBAC to safeguard security without compromising on UX. Add users to defined roles and grant permissions based on data sources, queries, automation rules, screens, or individual components.&lt;/p&gt;

&lt;p&gt;We even offer free SSO using OpenID, OAuth, Microsoft, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom plug-ins
&lt;/h3&gt;

&lt;p&gt;Budibase leads the pack for extensibility. Build your own data sources, automation actions, or components, and leverage them across your app projects with our dedicated CLI tools. Or, import community contributions from GitHub at the click of a button.&lt;/p&gt;

&lt;p&gt;Check out our custom &lt;a href="https://docs.budibase.com/docs/custom-plugin"&gt;plug-ins documentation&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  50+ free application templates
&lt;/h3&gt;

&lt;p&gt;Budibase is the ideal solution for building all kinds of web apps, utilities, internal tools, and more. To prove it, we’ve created over fifty free, fully-customizable &lt;a href="https://budibase.com/templates/"&gt;app templates&lt;/a&gt; to help get you started.&lt;/p&gt;

&lt;p&gt;Whether you need MySQL admin tools or a totally bespoke solution, sign up to Budibase today to start building professional apps the fast, easy way.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>How to Build a Google Sheets GUI in 5 Steps</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Fri, 28 Apr 2023 14:22:17 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/how-to-build-a-google-sheets-gui-in-5-steps-5h71</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/how-to-build-a-google-sheets-gui-in-5-steps-5h71</guid>
      <description>&lt;p&gt;A Google Sheets GUI can help you turn your spreadsheets into real apps.&lt;/p&gt;

&lt;p&gt;There are many useful free online tools for businesses. And Google Sheets is certainly one of the best of them. It allows you to get all the power of spreadsheets, databases, and scripting all at once.&lt;/p&gt;

&lt;p&gt;But at the end of the day, it’s still a spreadsheet tool.&lt;/p&gt;

&lt;p&gt;So you are limited to the grid design and unfiltered data entry. Users can add data in the wrong format. If you have multiple users, editing files can get confusing. There’s no user access control, advanced filtering, or search options.&lt;/p&gt;

&lt;p&gt;In general, it feels like you need to adjust all your workflows to work around the limitations, instead of having an app that adjusts to your needs.&lt;/p&gt;

&lt;p&gt;That’s where creating a Google Sheets GUI can come in handy.&lt;/p&gt;

&lt;p&gt;With a Graphical User Interface (GUI) you can use Google Sheets as a flexible data source. It includes options to manipulate data, collect data from other sites, or even run custom functions with Apps Script.&lt;/p&gt;

&lt;p&gt;At the same time, end users interact with your app using your Google Sheets GUI. Thus, you can control data entry, create custom views, add charts and metrics, and create different user levels.&lt;/p&gt;

&lt;p&gt;This allows you to get the best of both worlds. A flexible &lt;a href="https://budibase.com/blog/data/data-sources/"&gt;data source&lt;/a&gt;, along with a powerful user-facing app.&lt;/p&gt;

&lt;p&gt;Today, we’ll show how you can use a free open-source no-code builder to create your Google Sheets GUI in just 5 steps. By the end of the day, you’ll know how you can create beautiful Google Sheets apps, get data, edit it, search and much more.&lt;/p&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I create a dashboard in Google Sheets?
&lt;/h2&gt;

&lt;p&gt;You can use Google Sheets as a dashboard, by using custom styles, charts, and App Script to run functions. But it’s probably better and easier to use a free no-code builder such as Budibase to &lt;a href="https://budibase.com/blog/data/google-sheets-dashboard/"&gt;create a custom Dashboard for Google sheets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can follow the tips from this tutorial to build a dashboard in addition to a Google Sheets GUI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can Google Sheets be used as a database?
&lt;/h2&gt;

&lt;p&gt;Google Sheets is a great option for a free and simple database. With it, you can add data to your tables and use other apps to access it using the API.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I make Google Sheets more attractive?
&lt;/h2&gt;

&lt;p&gt;You can use the Google Sheets tools and create better designs. You can use options such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better font choices.&lt;/li&gt;
&lt;li&gt;A nice color palette.&lt;/li&gt;
&lt;li&gt;A modern design style.&lt;/li&gt;
&lt;li&gt;Custom icons.&lt;/li&gt;
&lt;li&gt;Fixed elements for improved readability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But at the end of the day, the best way to make Google Sheets look nice is not to use it for your design.&lt;/p&gt;

&lt;p&gt;At the end of the day, it’s your data source, and its design options are quite limited. Today, we are going to explore how you can make your custom design with a Google Sheets GUI.&lt;/p&gt;

&lt;p&gt;Our demo app is a simple CRM. It has two main menu items, and each of them has a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contacts

&lt;ul&gt;
&lt;li&gt;Add / edit&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Messages

&lt;ul&gt;
&lt;li&gt;Reply&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contacts screen looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--41PiFz23--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664958787/cms/01_dorll2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--41PiFz23--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664958787/cms/01_dorll2.webp" alt="Google Sheets GUI" title="Google Sheets GUI" width="800" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In it, you can quickly see some KPIs at the top of your Google Sheets GUI. Then you have filtering options and a table with your clients list.&lt;/p&gt;

&lt;p&gt;These are the possible statuses for your clients:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lead - You have their contact (maybe via a lead magnet) but they aren’t in your sales funnel.&lt;/li&gt;
&lt;li&gt;Funnel - They are in your sales funnel, so you are getting to know them, checking their problems, analyzing options, and negotiating.&lt;/li&gt;
&lt;li&gt;Active - These customers have bought already and you need to deliver or nurture them with pos-sales actions.&lt;/li&gt;
&lt;li&gt;Lost - These are customers who have received a proposal and rejected it, or who have ignored too many messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then if you click on “view” or “create new”, you’ll see a screen like this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1yT56Wgw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664958862/cms/02_emwvra.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1yT56Wgw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664958862/cms/02_emwvra.webp" alt="How to build a Google Sheets GUI" title="How to build a Google Sheets GUI" width="800" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This page is a form to add new customers. You can add form validation rules to make sure that all data points are correct, such as the email or last contact date.&lt;/p&gt;

&lt;p&gt;Then, there’s the messages screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l-zRNt3m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959023/cms/03_dgggxk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l-zRNt3m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959023/cms/03_dgggxk.webp" alt="Messages Screen" title="Messages Screen" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The messages screen includes a list of all messages, along with a filter. Notice how some messages have a “reply” button and some don’t.&lt;/p&gt;

&lt;p&gt;That’s because of the message status. The messages status in your Google Sheets GUI follows this pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Waiting - You have sent a message but the customer hasn’t replied.&lt;/li&gt;
&lt;li&gt;Replied - The customer replied, and you need to reply back.&lt;/li&gt;
&lt;li&gt;Closed - You have replied to the message and there are no further replies needed or you can send a new message and it has its own ID and status (starting with waiting again).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then if you click “reply” you’ll see this screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vbYbwDxd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959047/cms/04_uto864.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vbYbwDxd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959047/cms/04_uto864.webp" alt="Response screen" title="Response screen" width="800" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This seems like a simple form. But once you hit reply a lot of things will happen:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Budibase sends an email to your customer, using the title and contents from your form&lt;/li&gt;
&lt;li&gt;The current message is updated to closed.&lt;/li&gt;
&lt;li&gt;A new message is created with the “waiting” status.&lt;/li&gt;
&lt;li&gt;Your customer profile is updated with the last message date.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now let’s implement these screens&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Create a Google Sheets GUI app
&lt;/h2&gt;

&lt;p&gt;If you haven’t already, sign up for Budibase. Then create a new app and select Google Sheets as your data source.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I9iFj4AT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959069/cms/05_vwlqq4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I9iFj4AT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959069/cms/05_vwlqq4.webp" alt="Select a data source" title="Select a data source" width="800" height="1238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, authorize the app to access your Google Sheets data, add the sheet link and fetch the tables.&lt;/p&gt;

&lt;p&gt;Our demo app has the contacts sheet:&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VeiguW_n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959091/cms/06_qqe1ks.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VeiguW_n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959091/cms/06_qqe1ks.webp" alt="Google Sheets" title="Google Sheets" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The messages sheet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--51CF38TG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959112/cms/07_prilj1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--51CF38TG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959112/cms/07_prilj1.webp" alt="Google Sheets" title="Google Sheets" width="800" height="178"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the KPIs sheet:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bMqjUdb7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959131/cms/08_lje01i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bMqjUdb7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959131/cms/08_lje01i.webp" alt="Google Sheets" title="Google Sheets" width="534" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep in mind that you can use the Google Sheets functions to make your life much easier.&lt;/p&gt;

&lt;p&gt;Here are some examples.&lt;/p&gt;

&lt;p&gt;Instead of using JS code to create our KPIs, we just use the Google Sheets formulas to get them.&lt;/p&gt;

&lt;p&gt;Then, we include the customer email on the messages sheet. This saves you a lot of data processing to get the email based on the client ID. We are doing it with this function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=VLOOKUP(B2,contacts!$A$2:$D$5,4,1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In addition, since the messages and client IDs are just a list, you can use a formula to generate them (=ROW() ). This saves you a lot of trouble in getting the size of the current sheet, then adding a new row with that number + 1.&lt;/p&gt;

&lt;p&gt;Furthermore, sometimes you can use APIs or Apps Script to auto-update your Google Sheets. The sky's the limit, from something simple such as getting the current date, to complex operations like reading incoming emails from your inbox.&lt;/p&gt;

&lt;p&gt;In particular, if you can’t code, using the Google Sheets formulas is probably an easier route.&lt;/p&gt;

&lt;p&gt;Now let’s create the queries to load and update data to and from your Google Sheets GUI.&lt;/p&gt;

&lt;p&gt;You can create these queries:&lt;/p&gt;

&lt;h3&gt;
  
  
  close_message
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Function: Update&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bindings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sheet: messages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RowIndex:  id &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Row:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“status”: “closed”&lt;/p&gt;

&lt;h3&gt;
  
  
  add_message
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Function: Create&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bindings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contact_id&lt;/li&gt;
&lt;li&gt;Title&lt;/li&gt;
&lt;li&gt;Message&lt;/li&gt;
&lt;li&gt;Contact_email&lt;/li&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sheet: messages&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Row:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“id”: “=ROW()”,&lt;br&gt;
  “contact_id”: “ contact_id”,&lt;br&gt;
  “method”: “email”,&lt;br&gt;
  “date”: “date”,&lt;br&gt;
  “title”: “ title ”,&lt;br&gt;
  “message”: "message”,&lt;br&gt;
  “status”: “waiting”,&lt;br&gt;
  “contact_email”: “ contact_email”&lt;/p&gt;

&lt;h3&gt;
  
  
  Update_customer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Function: Update&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bindings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id&lt;/li&gt;
&lt;li&gt;Date&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sheet: contacts&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Row Index: id&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Row:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“last contact”: "date” &lt;/p&gt;

&lt;p&gt;That’s all for the “Data” tab. Now head over to the “Design” section and let’s create some screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Clients list screen
&lt;/h2&gt;

&lt;p&gt;You can create this page with stat cards and a table. In general, each data point of your page requires a data provider component.&lt;/p&gt;

&lt;p&gt;The data providers return your queries as an array, even if it’s just one item. So, to access the individual items you can use repeaters, tables, cards, or access the data provider data using bindings.&lt;/p&gt;

&lt;p&gt;Here is the elements tree for that page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IUaZHTCT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959199/cms/09_ijcerl.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IUaZHTCT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959199/cms/09_ijcerl.webp" alt="Google Sheets GUI" title="Google Sheets GUI" width="452" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, use the “&lt;em&gt;add component&lt;/em&gt;” button to add new components to your page. Then add the title container. In it, you can add your page’s title and then a button.&lt;/p&gt;

&lt;p&gt;Buttons don’t do anything by themselves, but you can assign actions to them. So, use this option for the button actions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HvtZCkgl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959220/cms/10_qst6op.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HvtZCkgl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959220/cms/10_qst6op.webp" alt="Button Actions" title="Button Actions" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, add the KPIs container, the data provider loading the KPIs sheet, and the repeater. Don’t forget to use the “direction” option to make all the stats cards appear on the same line:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VZEhC_Xl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959256/cms/11_o1j8f9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VZEhC_Xl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959256/cms/11_o1j8f9.webp" alt="Add a container" title="Add a containeer" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, each of the cards is showing one of your KPIs. You just need to use the name/value pairs, so the Google Sheets data is loaded:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: New Repeater.kpis.name

Value: New Repeater.kpis.value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, there’s the contacts table. You can use the data provider with the Google Sheets table “contacts”. Then add a dynamic filter and a table component to load and filter your data.&lt;/p&gt;

&lt;p&gt;You can add a “view” link by clicking on the contacts table, then add a component. This allows you to add a new column with that component (a link, a button, or anything else you want).&lt;/p&gt;

&lt;p&gt;Then use this URL:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/contacts/contacts Table.contacts._id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This URL makes it so that Budibase loads a different link for each row. They all follow the same pattern: /contacts/:ID.&lt;/p&gt;

&lt;p&gt;Thus, you can use this pattern on your screen. You can extract the ID from the URL and pass it to your data providers. Let’s see how you can do it on the next screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Add new and edit clients screens
&lt;/h2&gt;

&lt;p&gt;Both forms are very similar in their structures. Here is the components tree for the &lt;em&gt;add new&lt;/em&gt; screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iDr_jERa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959301/cms/12_m7apmw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iDr_jERa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959301/cms/12_m7apmw.webp" alt="Component Tree" title="Component Tree" width="470" height="806"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this is the components tree for the edit screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--t3yvWGIF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959326/cms/13_z8cs4w.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--t3yvWGIF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959326/cms/13_z8cs4w.webp" alt="Edit Screen" title="Edit Screen" width="460" height="986"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main difference between them is that the edit form uses a data provider to load the client information to pre-populate the form fields.&lt;/p&gt;

&lt;p&gt;Let’s build the edit form and you can use the same logic for the add new screen.&lt;/p&gt;

&lt;p&gt;Create a new screen and use this route:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/contacts/:id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is what allows Budibase to understand that whatever is after contacts/ should be stored in a variable called “ID”. You can access it using URL.id.&lt;/p&gt;

&lt;p&gt;This screen starts with a data provider. The trick here is to load the contacts table just like you did for the previous screen, but use this filter:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ld8VjU2n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959360/cms/14_hbbjwq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ld8VjU2n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959360/cms/14_hbbjwq.webp" alt="Google Sheets GUI" title="Google Sheets GUI" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This allows you to load only the current contact. And then you can use a repeater to expose the contact’s fields.&lt;/p&gt;

&lt;p&gt;You can create a form component, and use an update method with the “contacts” schema. This makes your life easier when you are creating the fields.&lt;/p&gt;

&lt;p&gt;The title container loads a title component loading the contact’s name, like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repeater.contacts.Name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, create a save button, with these actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Validate form.&lt;/li&gt;
&lt;li&gt;Save row - to the contacts table.&lt;/li&gt;
&lt;li&gt;Navigate to the contacts page.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the fields group, you can use the “update form fields” button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NvyxXQuM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959398/cms/15_cvhh8j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NvyxXQuM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959398/cms/15_cvhh8j.webp" alt="Field Group" title="Field Group" width="498" height="1148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This creates all the form fields automatically in your form.&lt;/p&gt;

&lt;p&gt;That’s all you need for the edit form. The &lt;em&gt;add new&lt;/em&gt; form is quite similar, just replace the save row action with the add new.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 4 - List messages
&lt;/h1&gt;

&lt;p&gt;The messages list screen is quite similar to the contacts list page. You can follow the same logic, just use this components tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R4PHf8BO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959419/cms/16_wlp9ru.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R4PHf8BO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959419/cms/16_wlp9ru.webp" alt="Component Tree" title="Component Tree" width="414" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can even use the option to copy the entire contacts page instead of creating a new one. Or maybe copy the components over. Click on the 3 dots next to the main container to access this.&lt;/p&gt;

&lt;p&gt;The only difference is that this page is going to load the reply link only for messages that aren’t closed.&lt;/p&gt;

&lt;p&gt;You can do it using conditionality, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A9cVlPCk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959453/cms/17_d3edvg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9cVlPCk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959453/cms/17_d3edvg.webp" alt="Conditionality editor" title="Conditionality editor" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So you hide the link if messages Table.messages.status is closed.&lt;/p&gt;

&lt;p&gt;Also, don’t forget to update the messages link to be:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/messages/messages Table.messages._id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And update the add new button as well.&lt;/p&gt;

&lt;p&gt;Let’s build the message reply form.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 - Automatically email replies from your Google Sheets GUI
&lt;/h2&gt;

&lt;p&gt;On the surface, the reply form is quite similar to the contacts edit page. You can create a new page and use the /messages/:id route.&lt;/p&gt;

&lt;p&gt;Then use this components tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zlZ1xzBs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959484/cms/18_upe8nz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zlZ1xzBs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959484/cms/18_upe8nz.webp" alt="Data provider" title="Data provider" width="450" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The overall page setup is quite similar, just make sure you use a long text form instead of a simple text field.&lt;/p&gt;

&lt;p&gt;Also, in this case, you can use a custom schema for your form, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s-8ohIV0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959503/cms/19_zhvapm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s-8ohIV0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959503/cms/19_zhvapm.webp" alt="Form Schema" title="Form Schema" width="514" height="1026"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But the general setup is the same. Use a data provider, use the filtering options matching the URL, and add the components.&lt;/p&gt;

&lt;p&gt;The real magic is in the “send reply” button.&lt;/p&gt;

&lt;p&gt;But before we do this, you need to do 2 things.&lt;/p&gt;

&lt;h3&gt;
  
  
  SMTP setup
&lt;/h3&gt;

&lt;p&gt;Go to your main Budibase screen - before the app selection - and click on “Email”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TvKWyjed--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959525/cms/20_gg4hac.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TvKWyjed--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959525/cms/20_gg4hac.webp" alt="SMTP setup" title="SMTP setup" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add your email settings there and save. If everything is ok you’ll see a green confirmation message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automation setup
&lt;/h3&gt;

&lt;p&gt;Now, go back to the app edit screen. At the top there are three tabs, &lt;em&gt;Data, Design,&lt;/em&gt; and &lt;em&gt;Automate&lt;/em&gt;. Click on &lt;em&gt;Automate.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You can use this section to run automated actions in your app. But you can use it to create actions that are triggered by your app as well.&lt;/p&gt;

&lt;p&gt;Create a new automation called “send email” that starts using an App Action:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rBeNQSeV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959551/cms/21_xtyep4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rBeNQSeV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1664959551/cms/21_xtyep4.webp" alt="Create an automation" title="Create an automation" width="800" height="1016"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add three fields to it, email, subject, and text. Then on the “&lt;em&gt;do this&lt;/em&gt;” options, select send email, use the “&lt;em&gt;send to”&lt;/em&gt; field as the customer email, add the subject, and add the HTML contents as the text using their bindings:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trigger.fields.email

trigger.fields.subject

trigger.fields.text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Save it and test it. You can add a backup email as BCC, which is hidden from customers. This allows you to make sure that all emails are being sent correctly as you get a copy in your inbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Send Reply action
&lt;/h3&gt;

&lt;p&gt;Now that the email setup is ready, let’s configure your “send reply” actions.&lt;/p&gt;

&lt;p&gt;You can use these actions:&lt;/p&gt;

&lt;p&gt;Trigger Automation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use an existing automation&lt;/li&gt;
&lt;li&gt;Automation: Send email&lt;/li&gt;
&lt;li&gt;Fields

&lt;ul&gt;
&lt;li&gt;Email: Repeater.messages.contact_email&lt;/li&gt;
&lt;li&gt;Subject: Form.Fields.title&lt;/li&gt;
&lt;li&gt;Text: Form.Fields.message&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Execute Query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Datasource: Google Sheets&lt;/li&gt;
&lt;li&gt;Query: close_message

&lt;ul&gt;
&lt;li&gt;Bindings:&lt;/li&gt;
&lt;li&gt;Id: URL.id&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Execute Query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Datasource: Google Sheets&lt;/li&gt;
&lt;li&gt;Query: add_message

&lt;ul&gt;
&lt;li&gt;Contact_id: Repeater.messages.contact_id&lt;/li&gt;
&lt;li&gt;Title: Form.Fields.title&lt;/li&gt;
&lt;li&gt;Message: Form.Fields.message&lt;/li&gt;
&lt;li&gt;Contact_email: Repeater.messages.contact_email&lt;/li&gt;
&lt;li&gt;Date (JS code): return new Date().toLocaleDateString()&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Execute Query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Datasource: Google Sheets&lt;/li&gt;
&lt;li&gt;Query: update_customer

&lt;ul&gt;
&lt;li&gt;Id: Repeater.messages.contact_id&lt;/li&gt;
&lt;li&gt;Date (JS code): return new Date().toLocaleDateString()&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These queries perform the actions that we mentioned before. You send the email. Then you update the current message to closed. Next, you add a new message for the newly created email. And finally, you update your customer’s last contact.&lt;/p&gt;

&lt;p&gt;That’s it. Your Google Sheets GUI is ready.&lt;/p&gt;

&lt;p&gt;You might also like our guides to building a &lt;a href="https://budibase.com/blog/tutorials/rest-api-gui/"&gt;REST API GUI&lt;/a&gt; or a &lt;a href="https://budibase.com/blog/data/google-sheets-dashboard/"&gt;Google Sheets Dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Google Sheets GUI with Budibase
&lt;/h2&gt;

&lt;p&gt;Today we looked into how you can use Google Sheets as a database, and create a custom app to manage your data without coding. With your Google Sheets GUI, you can read Sheets data, manipulate it, and perform other actions such as sending emails or connecting to APIs.&lt;/p&gt;

&lt;p&gt;Take a look at our ultimate guide to &lt;a href="https://budibase.com/blog/tutorials/database-gui/"&gt;database GUIs&lt;/a&gt; for more inspiration.&lt;/p&gt;

&lt;p&gt;We hope you enjoyed it, and see you again next time!&lt;/p&gt;

</description>
      <category>lowcode</category>
      <category>tutorial</category>
      <category>gui</category>
      <category>googlesheets</category>
    </item>
    <item>
      <title>How to Build a Dashboard in 5 Steps with Budibase</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Fri, 28 Apr 2023 10:44:35 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/how-to-build-a-dashboard-in-5-steps-with-budibase-5cfk</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/how-to-build-a-dashboard-in-5-steps-with-budibase-5cfk</guid>
      <description>&lt;p&gt;Knowing how to build a dashboard is one of the core competencies for most knowledge-based jobs. Dashboards are the difference between raw data and clear, actionable insights.&lt;/p&gt;

&lt;p&gt;Most of us can pull up a couple of charts based on a spreadsheet - or even use a visualization platform like Looker to create basic reports. But what happens when your business data doesn’t fit neatly into one of these tools?&lt;/p&gt;

&lt;p&gt;See, businesses hold more data than ever before, but it’s rarely in just one place, in just one format. Instead, we often need to cut through a mess of databases, spreadsheets, SaaS platforms, and APIs to generate the insights we need.&lt;/p&gt;

&lt;p&gt;This is where the reporting tools you’re used to can fall down - and, until recently - you might have found yourself running to internal developers to whip up a custom solution.&lt;/p&gt;

&lt;p&gt;Today, we’ll see how Budibase empowers professionals to build advanced custom dashboards - with &lt;em&gt;multiple data sources&lt;/em&gt; - for themselves. Even better - to do it in a fraction of the time you’d expect.&lt;/p&gt;

&lt;p&gt;First, though, let’s think about a little bit of context.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a dashboard?
&lt;/h2&gt;

&lt;p&gt;A dashboard is a very simple application that reports on key business data in real time. At least, they’re simple in theory, but in the real world, things can easily get a bit more complex.&lt;/p&gt;

&lt;p&gt;The idea is that decision-makers have a UI that they can quickly access to check out a fixed report, that updates automatically - usually with a relatively small number of actual data points. &lt;/p&gt;

&lt;p&gt;This means they don’t need to go searching through the data themselves every time they need a summary of whatever we’re reporting on - say, online sales figures for the month.&lt;/p&gt;

&lt;p&gt;At a technical level, dashboards are made up of three layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The data layer&lt;/strong&gt; - Where our data is stored and queried.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The presentation layer&lt;/strong&gt; - The interface that users access to check out our report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The process layer&lt;/strong&gt; - Where we carry out any transformations or processing to get our data from the data layer to the presentation layer, in the format we need.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The process layer becomes more important the more complex our data layer is. For example, if we’re querying &lt;em&gt;one&lt;/em&gt; database to create our report, we might not need much processing. &lt;/p&gt;

&lt;p&gt;If we query &lt;em&gt;two&lt;/em&gt; separate datasets, we’ll need to work a little bit of magic behind the scenes to unify these into a comprehensible report - and so on.&lt;/p&gt;

&lt;p&gt;This is where traditional reporting tools, especially spreadsheets, fall down - they just don’t offer us enough capability to process and collate external data.&lt;/p&gt;

&lt;p&gt;Therefore, anything but the most basic dashboards often need to be custom-built - typically as web apps or within more complex data visualization tools.&lt;/p&gt;

&lt;p&gt;As we’ll see, Budibase offers a powerful alternative. You might also like our guide to how to build a &lt;a href="https://budibase.com/blog/crud-app/"&gt;crud app&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  When are dashboards used?
&lt;/h3&gt;

&lt;p&gt;Dashboards have countless use cases. These comprise any situation where colleagues need fast, easy access to a real-time, fixed report. Often, this relates to internal goals and KPIs, or other progress updates.&lt;/p&gt;

&lt;p&gt;So, for example, departmental leaders will often use dashboards to understand how their team is progressing toward top-level strategic goals. For example, monthly revenue figures within sales or marketing departments.&lt;/p&gt;

&lt;p&gt;In a product team, this could be used to monitor GitHub metrics in a faster, more intuitive way than we would get from GitHub itself.&lt;/p&gt;

&lt;p&gt;Alternatively, dashboards can also be used to report on &lt;em&gt;status&lt;/em&gt; updates - either internally or in a customer-facing context.&lt;/p&gt;

&lt;p&gt;For instance, your IT team needs to make sure that all of your network infrastructure is running correctly. Uptime dashboards provide a fast, easy way to do this, as well as expediting the process of diagnosing problems if they do occur.&lt;/p&gt;

&lt;p&gt;Or, in a customer-facing context, we might use a simple web-based dashboard to let users check out the status of your new feature releases, or even check out important data relating to &lt;em&gt;their own&lt;/em&gt; accounts.&lt;/p&gt;

&lt;p&gt;The possibilities are basically endless.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a dashboard with multiple data sources
&lt;/h2&gt;

&lt;p&gt;As we said earlier, things begin to get a bit trickier when it comes to how to build a dashboard that requires a more complex data model.&lt;/p&gt;

&lt;p&gt;This is because the more data sources we draw on, the more collation, aggregation, and other transformation functions we’ll need to get our data into a presentable state. &lt;/p&gt;

&lt;p&gt;There are a couple of key things that can cause us problems here.&lt;/p&gt;

&lt;p&gt;One is if we store data on the same entities in separate, unrelated data sources. For example, if we have a database with our customer and order information, but we also need to draw on our fulfillment platform for data relating to shipping times - with no formal link between the two.&lt;/p&gt;

&lt;p&gt;In an ideal world, we’d have a preconfigured single source of truth to help us here, but the reality for most businesses is that we won’t. So, we’ll need to query both and then use our process layer to collate data from each about individual customers and orders.&lt;/p&gt;

&lt;p&gt;The other scenario is if the required data sets differ in terms of their format.&lt;/p&gt;

&lt;p&gt;Say we were building a sales dashboard that comprised data from our ecommerce site for online orders and our invoicing platform for enterprise sales. Even minor variations in how dates are formatted between these tools could skew our figures.&lt;/p&gt;

&lt;p&gt;So, we need transformations to achieve consistency before we can present our data.&lt;/p&gt;

&lt;p&gt;In terms of implementation, one nice thing about dashboards is that we’ve got a few different options for performing transformations - depending on how we build our dashboards, that is.&lt;/p&gt;

&lt;p&gt;Broadly speaking, these are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Performing transformations within our data layer&lt;/strong&gt; - for example, creating aggregated data sets that can be queried.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performing transformations with a separate process layer&lt;/strong&gt; - for example, building them into our queries, or applying them to the output data before it’s called by UI elements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performing transformations within our UI&lt;/strong&gt; - for example, applying simple manipulations to individual values that are displayed by particular UI elements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these options offers distinct pros and cons, so they’re best leveraged in slightly different contexts. For instance, if we’re going to regularly apply the same transformation in different dashboards, it makes sense to create an aggregate data source.&lt;/p&gt;

&lt;p&gt;If we’re going to utilize certain transformations throughout a single dashboard, performing these in a dedicated process layer is likely the best option.&lt;/p&gt;

&lt;p&gt;For simple transformations that we only need in specific places, like rounding some values or basic mathematical functions, implementation at the UI level is often our best option.&lt;/p&gt;

&lt;p&gt;Therefore, to build dashboards effectively, we need to be able to take advantage of a combination of these techniques. &lt;/p&gt;

&lt;p&gt;We’ll see how Budibase handles each in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a dashboard in 5 steps
&lt;/h2&gt;

&lt;p&gt;Enough of the theory. You came here to learn how to build a dashboard - and that’s what we want to focus on.&lt;/p&gt;

&lt;p&gt;To show off just what Budibase is capable of, we’ve opted for a relatively tricky use case. We’re going to build an online orders dashboard that draws on &lt;em&gt;three different&lt;/em&gt; data sets.&lt;/p&gt;

&lt;p&gt;These are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Postgres database that stores our online order data.&lt;/li&gt;
&lt;li&gt;A Google Sheets table that stores our display advertising spend.&lt;/li&gt;
&lt;li&gt;A REST API that retrieves customer complaints data from an external tool - in our case, a separate Budibase app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s what the finished product will look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7p1hCcz0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_1_tstu7m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7p1hCcz0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_1_tstu7m.webp" alt="How to build a dashboard" title="how to build a dashboard" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have two tabs. The first, displayed above displays daily and total order volumes within a user-specified date range. Users can also filter by specific items.&lt;/p&gt;

&lt;p&gt;The second also leverages a user-specified date range, this time showing our daily advertising spend and the return on this over the period selected:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uSOYDqZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_2_ggt2v2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uSOYDqZc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_2_ggt2v2.webp" alt="dynamic dashboard" title="dynamic dashboard" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s actually quite a bit going on behind the scenes here, so let’s take things one step at a time.&lt;/p&gt;

&lt;p&gt;Here’s how to build a dashboard in Budibase in 5 steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Define your goals
&lt;/h3&gt;

&lt;p&gt;The first step when it comes to building a dashboard is figuring out what we want to achieve. As far as requirements-gathering exercises go, this is relatively straightforward. The big decision is simply &lt;em&gt;what data&lt;/em&gt; we need to communicate.&lt;/p&gt;

&lt;p&gt;In our case, we want to display the following for specified date ranges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily online sales figures.&lt;/li&gt;
&lt;li&gt;Sales volumes and total values.&lt;/li&gt;
&lt;li&gt;Average daily order values.&lt;/li&gt;
&lt;li&gt;Total customer complaints.&lt;/li&gt;
&lt;li&gt;Daily advertising spend.&lt;/li&gt;
&lt;li&gt;Total advertising spend.&lt;/li&gt;
&lt;li&gt;Total return on investment.&lt;/li&gt;
&lt;li&gt;Daily return on investment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once we know this information, our next step is to figure out &lt;em&gt;where&lt;/em&gt; we can get the data that we need - taking into account any additional functionality we need for user interactions. For example, we want users to be able to filter our dashboard by individual items.&lt;/p&gt;

&lt;p&gt;Therefore, we’ll need to account for this when choosing data sources.&lt;/p&gt;

&lt;p&gt;We’ve already outlined where our data is going to come from, but in the real world, you’d need to do a bit of research on your business’s own data assets to figure this out.&lt;/p&gt;

&lt;p&gt;Once we’ve determined exactly what we want to build, it’s time to create a new Budibase application.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Create your data layer
&lt;/h3&gt;

&lt;p&gt;When we create a Budibase app, we’ll be prompted to choose a data source:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cp5FSQwJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_3_ygujwk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cp5FSQwJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_3_ygujwk.webp" alt="choose a data source" title="choose a data source" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recall, we’re going to use a Postgres database, a REST API request, and a Google Sheets table - so we’ll actually need to create three separate data sources.&lt;/p&gt;

&lt;p&gt;We’ll start with our Postgres table. When we click on Postgres, we’ll see the following form where we can enter our credentials and fetch our tables:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nVGqcWF3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_Dashboard_4_jinvix.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nVGqcWF3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_Dashboard_4_jinvix.webp" alt="postgres fetch" title="postgres fetch" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our demo database only has one table, but if we had multiple, we could select the specific ones we need to access. &lt;/p&gt;

&lt;p&gt;Fetching tables imports everything we need into the Budibase builder UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--INOLksJm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_Dashboard_5_kq4pju.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--INOLksJm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_Dashboard_5_kq4pju.webp" alt="Postgres table" title="postgres table" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, our Google Sheets table. This time, we only need to sign in to Google and paste in the URL of the spreadsheet we want, then we can fetch our tables just like before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tlZMyyso--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_6_ehlr2y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tlZMyyso--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_6_ehlr2y.webp" alt="Google Sheets Dashboard" title="google sheets dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Et voila.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PbCgBMZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_7_gij2li.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PbCgBMZT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_7_gij2li.webp" alt="Google Sheets Table" title="Google Sheets Dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, our REST data source. We’re using this to connect to an external Budibase app, but you could do something similar with any other platform that offers a public API.&lt;/p&gt;

&lt;p&gt;We’re using a POST request targeting a URL with the format:&lt;/p&gt;

&lt;p&gt;​ &lt;em&gt;baseUrl/tables/:tableId/rows/search&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;em&gt;baseURL&lt;/em&gt; is the location of our Budibase tenant - so something like &lt;em&gt;your-tenant.budibase.app&lt;/em&gt; for a cloud-based account. &lt;em&gt;:tableId&lt;/em&gt; is the ID of the table we want to target.&lt;/p&gt;

&lt;p&gt;We’ll pop this into the URL box and then add two headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;x-budibase-api-key&lt;/strong&gt; - for our unique API key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;x-budibase-app-id&lt;/strong&gt;  - for the ID of the specific Budibase app we want to target.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9J2FwQqz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_8_ykh6th.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9J2FwQqz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_8_ykh6th.webp" alt="API Key" title="API headers" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can get our API key from the menu in the top right corner of the screen and the app ID from the &lt;em&gt;manage&lt;/em&gt; section of any published app.&lt;/p&gt;

&lt;p&gt;Once we’ve filled in those details, we can send the request to make sure it works. This is the JSON object that our’s returns:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dJ7Mc-pq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_Dashboard_9_y2hxdv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dJ7Mc-pq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_Dashboard_9_y2hxdv.webp" alt="how to build a dashboard" title="how to build a dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For context, here’s what the table looks like in the app we’re requesting the data from:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XVpxHyYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_10_lgs9u2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XVpxHyYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_10_lgs9u2.webp" alt="How to build a dashboard" title="How to build a dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, now all of our data sources are connected, we can start performing any transformations we need to get the information into a usable format. &lt;/p&gt;

&lt;h3&gt;
  
  
  3. Perform transformations
&lt;/h3&gt;

&lt;p&gt;We’re going to apply the bulk of our transformations within the &lt;em&gt;Data&lt;/em&gt; section of Budibase. In terms of traditional app architecture, this will form the equivalent of our &lt;em&gt;process layer&lt;/em&gt;. We could also supplement this with our built-in &lt;em&gt;Automation&lt;/em&gt; editor.&lt;/p&gt;

&lt;p&gt;But, for our purposes today, we can do everything we need without going down that road, so there’s no sense in over-complicating our app.&lt;/p&gt;

&lt;p&gt;Here are the transformations that we applied to each data source.&lt;/p&gt;

&lt;h4&gt;
  
  
  Postgres
&lt;/h4&gt;

&lt;p&gt;The daily sales figures for our dashboard can be accessed from our Postgres table as is. However, we need a couple of custom queries to get the summary figures for our order count, total value, and average value.&lt;/p&gt;

&lt;p&gt;We’ll use one query to do this, filtered by &lt;em&gt;date&lt;/em&gt;, and a second to do the same, filtered by &lt;em&gt;date and item name.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So, we start with a SELECT statement for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SUM(“value”)&lt;/strong&gt; - the total of all of the values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COUNT(“id”)&lt;/strong&gt; - the number of unique IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AVG(“value”)&lt;/strong&gt; - the average of all of the IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;COUNT(“date”) AS “days”&lt;/strong&gt; - the total number of unique dates, presented as a variable called “days”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To filter this by date, we need to create two bindable values that we can include in a WHERE clause - for user-defined start and end dates:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2bchn5px--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_11_ihjf8o.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2bchn5px--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_11_ihjf8o.webp" alt="How to build a dashboard" title="how to build a dashboard" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ve called these &lt;em&gt;startDate&lt;/em&gt; and &lt;em&gt;endDate&lt;/em&gt;, and given them default values that we know match some of our rows. We can call these in our customer query as &lt;em&gt;startDate&lt;/em&gt; and &lt;em&gt;endDate&lt;/em&gt; respectively.&lt;/p&gt;

&lt;p&gt;Our completed query looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jdCWI222--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_12_vjvkq9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jdCWI222--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_12_vjvkq9.webp" alt="SQL dashboard" title="Custom query" width="670" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;transformer&lt;/em&gt; box is set to &lt;em&gt;return data&lt;/em&gt; by default, which we’ll keep. So, when we run the query, we get the following object:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vup3BXVb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_13_lqg4gu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vup3BXVb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_13_lqg4gu.webp" alt="How to build a sql dashboard" title="How to build a sql dashboard" width="673" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This looks good.&lt;/p&gt;

&lt;p&gt;We can then duplicate this query, and add a third binding called &lt;em&gt;itemName&lt;/em&gt; to the new version - and add this to our WHERE clause, so our query this time is:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--byLAZfU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_14_xezarp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--byLAZfU7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_14_xezarp.webp" alt="WHERE clause" title="WHERE clause" width="666" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s everything we need for our Postgres table.&lt;/p&gt;

&lt;h4&gt;
  
  
  Google Sheets
&lt;/h4&gt;

&lt;p&gt;Next, we’ll check out Google Sheets. This time, we just need one custom query - again, to get the same statistics as before for our specific date range. We’ll start by creating bindings again for &lt;em&gt;startDate&lt;/em&gt; and &lt;em&gt;endDate&lt;/em&gt; - this time with arbitrary default values:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6XbjJUn7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_15_fgngxh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6XbjJUn7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_15_fgngxh.webp" alt="Google Sheets Dashboard" title="Google Sheets Dashboard" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can’t query Google Sheets with SQL, so we need to use JavaScript to perform our desired transformations on the data that’s returned when we read the table.&lt;/p&gt;

&lt;p&gt;We need to do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create a new variable called &lt;em&gt;responseData&lt;/em&gt; and set this to the data that Google Sheets provides.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create an empty array called &lt;em&gt;outputData.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create three variables called &lt;em&gt;totalValue, count,&lt;/em&gt; and &lt;em&gt;avgValue&lt;/em&gt; - and set each to 0.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use a &lt;em&gt;for loop&lt;/em&gt; to iterate over &lt;em&gt;responseData&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use an &lt;em&gt;if statement&lt;/em&gt; to assess if each row’s date falls between our specific range. If so:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;Add each of these rows’ &lt;em&gt;Spend&lt;/em&gt; attribute to our &lt;em&gt;totalValue&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ul&gt;
&lt;li&gt;Increment our &lt;em&gt;count&lt;/em&gt; variable.&lt;/li&gt;
&lt;li&gt;Set &lt;em&gt;avgValue&lt;/em&gt; to (&lt;em&gt;totalValue / count&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Wrap these values in an object called &lt;em&gt;rowData&lt;/em&gt;, with the keys &lt;em&gt;“sum”, “count”&lt;/em&gt;, and “&lt;em&gt;avg”&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After our &lt;em&gt;for loop&lt;/em&gt; the final value for &lt;em&gt;rowData&lt;/em&gt; is pushed to the &lt;em&gt;outputData&lt;/em&gt; array.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Return &lt;em&gt;outputData&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Budibase JS editor is a little small for pulling clear screengrabs, so here’s what this would look like in VS code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qDZBRFuG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674085/cms/build-a-dashboard/Build_a_dashboard_16_mw0xs4.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qDZBRFuG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674085/cms/build-a-dashboard/Build_a_dashboard_16_mw0xs4.webp" alt="Data transformation" title="custom transformer" width="796" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s the JSON response when we run our query:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jkQ9PoZg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_17_yytjok.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jkQ9PoZg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_17_yytjok.webp" alt="how to build a dashboard" title="how to build a dashboard" width="685" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that in JavaScript, we access our bindings with the expression &lt;em&gt;params.bindingName&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Hit &lt;em&gt;Save Query&lt;/em&gt; and we’re done.&lt;/p&gt;

&lt;h4&gt;
  
  
  REST API
&lt;/h4&gt;

&lt;p&gt;Lastly, we need to apply some similar transformations to our REST response. &lt;/p&gt;

&lt;p&gt;First, if you were eagle-eyed, you’ll have noticed that the response earlier was wrapped in an object called &lt;em&gt;“data”&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s-IRmHT---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_18_aol77y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s-IRmHT---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_18_aol77y.webp" alt="REST response" title="REST response" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each entity also includes some attributes we don’t strictly need, like the table’s autogenerated row &lt;em&gt;_id&lt;/em&gt; and even the &lt;em&gt;tableID&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;So, we’ll do a little bit of cleaning up before we start using this in our UI.&lt;/p&gt;

&lt;p&gt;We’ll start by creating the exact same &lt;em&gt;startDate&lt;/em&gt; and &lt;em&gt;endDate&lt;/em&gt; bindings as before. We’ll then use basically the same script as before, but with a couple of key changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We set &lt;em&gt;responseData&lt;/em&gt; to &lt;em&gt;data[“data”]&lt;/em&gt; - to access the contents of the “&lt;em&gt;data”&lt;/em&gt; object - and then create an empty array called &lt;em&gt;outputData&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;We iterate over &lt;em&gt;responseData&lt;/em&gt; and add the same &lt;em&gt;if statement&lt;/em&gt; as before.&lt;/li&gt;
&lt;li&gt;Inside the &lt;em&gt;if statement&lt;/em&gt; we create a &lt;em&gt;rowData&lt;/em&gt; object with the keys &lt;em&gt;“date”, customerID”&lt;/em&gt;, &lt;em&gt;“complaintText”&lt;/em&gt;, and &lt;em&gt;“orderID”&lt;/em&gt;, with values for the respective attributes from the current row in the iteration.&lt;/li&gt;
&lt;li&gt;We then push &lt;em&gt;rowData&lt;/em&gt; to &lt;em&gt;outputData&lt;/em&gt; inside the &lt;em&gt;for loop&lt;/em&gt;, adding each row to our array.&lt;/li&gt;
&lt;li&gt;We return &lt;em&gt;rowData&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, we’re how this would look in VS Code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M5iCm4WE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_19_m5f0wb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M5iCm4WE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_19_m5f0wb.webp" alt="REST transformation" title="REST transformation" width="758" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here’s the output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ylk2zz2W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_20_zdauze.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ylk2zz2W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674091/cms/build-a-dashboard/Build_a_dashboard_20_zdauze.webp" alt="Complaints data" title="complaints data" width="800" height="729"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, we’ll duplicate our request, and write a new transformer to grab &lt;em&gt;count&lt;/em&gt; of our complaints for a given date range.&lt;/p&gt;

&lt;p&gt;This works just like the stats in our Google Sheets query, so we won’t go through it all again. Here’s the code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dB5VPugC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_21_iftzsd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dB5VPugC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_21_iftzsd.webp" alt="How to build a dashboard" title="How to build a dashboard" width="765" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ztZmxLAx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_22_aledvu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ztZmxLAx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_22_aledvu.webp" alt="How to build a dashboard" title="how to build a dashboard" width="800" height="728"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll do a little bit more aggregation and reformatting as we build out our UI, but that’s the bulk of our transformation complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Build your UI
&lt;/h3&gt;

&lt;p&gt;Next, we can start building our UI. Our dashboard is actually just one screen, but we’ve created tabs to flick between two slightly different reports.&lt;/p&gt;

&lt;p&gt;Here’s the component tree for the overall layout and the &lt;em&gt;Online Orders&lt;/em&gt; tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v9Rirsou--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_23_atbwyg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v9Rirsou--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674090/cms/build-a-dashboard/Build_a_dashboard_23_atbwyg.webp" alt="component tree" title="component tree" width="262" height="801"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, we’ve just got one bar chart, a few stat cards, and three data providers with repeaters.&lt;/p&gt;

&lt;p&gt;Here’s what that looks like in situ:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2r5H4yZI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_24_xxo6qb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2r5H4yZI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_24_xxo6qb.webp" alt="How to build a dashboard" title="How to build a dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see how we’d build this, one step at a time.&lt;/p&gt;

&lt;h4&gt;
  
  
  Tabs and layout
&lt;/h4&gt;

&lt;p&gt;We’ll start by creating a form to wrap everything, as this will allow us to filter data a little bit later using form field components. &lt;/p&gt;

&lt;p&gt;We’ve called this &lt;em&gt;Master Form&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Inside, we’ve added a &lt;em&gt;Super Tabs&lt;/em&gt; component - which is actually a custom plug-in. It’s publicly available though, so you can grab the URL from our plug-ins repo and paste it into your accounts plug-ins section. &lt;/p&gt;

&lt;p&gt;Basically, this creates tabs based on any components nested directly inside. It’s pretty nifty.&lt;/p&gt;

&lt;p&gt;We’ve put two &lt;em&gt;containers&lt;/em&gt; inside our &lt;em&gt;Super Tabs&lt;/em&gt;, called &lt;em&gt;Online Orders&lt;/em&gt; and &lt;em&gt;Ad Spend&lt;/em&gt;. Here’s how this looks so far:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ji0qJfnY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_25_cliadk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ji0qJfnY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_25_cliadk.webp" alt="Tabs" title="Tabs" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll build our two reports inside these tabs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Cards
&lt;/h4&gt;

&lt;p&gt;Next we’ll add our stat cards. First, we add a &lt;em&gt;Data Provider&lt;/em&gt; set to our &lt;em&gt;SUM, COUNT, AVG by Date&lt;/em&gt; query to our orders table. Inside this, we’ll add a horizontal repeater and three stat cards with titles &lt;em&gt;Total Orders,&lt;/em&gt; &lt;em&gt;Total Value&lt;/em&gt;, and &lt;em&gt;Average Value&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We’ll use handlebars expressions to bind the values for these to the &lt;em&gt;sum, count&lt;/em&gt;, and &lt;em&gt;avg&lt;/em&gt; attributes in the query response respectively, and add in dollar signs where we need to. &lt;/p&gt;

&lt;p&gt;For example: &lt;em&gt;stats Repeater.Sum, Count, AVG Orders By Date.count&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Our fourth card will be the count of customer complaints, so we need an extra data provider and repeater pair inside our existing repeater to access this. We’ll set our new provider to the &lt;em&gt;Complaints Data Stats&lt;/em&gt; query, and add a fourth stat card inside its repeater.&lt;/p&gt;

&lt;p&gt;The title for this is &lt;em&gt;Complaints&lt;/em&gt; and the value is bound to the &lt;em&gt;count&lt;/em&gt; attribute.&lt;/p&gt;

&lt;p&gt;This is what our cards look like for the &lt;em&gt;Online Orders&lt;/em&gt; tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HtUvhgY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_26_uz5ydu.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HtUvhgY0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_26_uz5ydu.webp" alt="How to build a dashboard" title="How to build a dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll copy this and paste it into the &lt;em&gt;Ad Spend Tab&lt;/em&gt;, then change the first data provider to the &lt;em&gt;Ad Spend Stats&lt;/em&gt; query. This time, our cards are going to show:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Total Spend.&lt;/li&gt;
&lt;li&gt;Total Return.&lt;/li&gt;
&lt;li&gt;Average Daily Spend.&lt;/li&gt;
&lt;li&gt;Daily Return.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first and third cards can be bound to the &lt;em&gt;sum&lt;/em&gt; and &lt;em&gt;avg&lt;/em&gt; attributes from our updated existing data provider, plus dollar signs.&lt;/p&gt;

&lt;p&gt;Cards two and four will need to be wrapped in data providers for our online order stats. So we’ll wrap these in data provider and repeater pairs, with the data source set to our &lt;em&gt;SUM, COUNT, AVG by DATE&lt;/em&gt; query.&lt;/p&gt;

&lt;p&gt;We’re going to use a bit of JavaScript to get aggregate values for these. &lt;/p&gt;

&lt;p&gt;So, our total return is the revenue we generated - which is stored in Postgres - minus what we spend on advertising, as stored in the Google Sheets Table.&lt;/p&gt;

&lt;p&gt;We use the following JavaScript as the card’s value, returning it to two decimal places:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rFin5jFu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_27_r16at9.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rFin5jFu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_27_r16at9.webp" alt="UI Transformation" title="Transformation in UI" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then for the daily average return card, we use a JavaScript binding to take the average advertising spend away from our daily average revenue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AaGceAi6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_28_uixmp0.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AaGceAi6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_28_uixmp0.webp" alt="Average Revenue" title="Average Revenue" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s our cards most of the way there. We’ll update these to account for our user-defined filtering shortly to finish them off.&lt;/p&gt;

&lt;h4&gt;
  
  
  Charts
&lt;/h4&gt;

&lt;p&gt;Next, we’ll add a &lt;em&gt;Chart Block&lt;/em&gt; to the bottom of each report. For the first, we’ll set the &lt;em&gt;data&lt;/em&gt; to &lt;em&gt;onlineOrders&lt;/em&gt;, the &lt;em&gt;label column&lt;/em&gt; to &lt;em&gt;date,&lt;/em&gt; and the &lt;em&gt;data column&lt;/em&gt; to value. We’ll also change the &lt;em&gt;height&lt;/em&gt; setting from 400 to 600, to make it a little taller.&lt;/p&gt;

&lt;p&gt;Then, we’ll do the same with our Ad Spend tab, this time using &lt;em&gt;Ad Spend&lt;/em&gt; as the data, &lt;em&gt;Date&lt;/em&gt; as the label, and &lt;em&gt;Spend&lt;/em&gt; as the data column.&lt;/p&gt;

&lt;p&gt;And that’s it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y1WUiDeU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_29_ukydth.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y1WUiDeU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_29_ukydth.webp" alt="Dashboard UI" title="Dashboard UI" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  User filtering
&lt;/h4&gt;

&lt;p&gt;Next, we’ll add our filtering UI. On the &lt;em&gt;Online Orders&lt;/em&gt; tab, users can set a date range and filter by item name. On the &lt;em&gt;Ad Spend&lt;/em&gt; tab, they can just set a date range.&lt;/p&gt;

&lt;p&gt;We’ll create a horizontal container at the top of our orders tab. Inside this we’ll place a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Title component.&lt;/li&gt;
&lt;li&gt;Two date pickers inside another horizontal container, with their fields set to &lt;em&gt;startDate&lt;/em&gt; and &lt;em&gt;endDate&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;A text box, with the field set to &lt;em&gt;itemName&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iCwb-MgW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_30_nvpyix.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iCwb-MgW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674088/cms/build-a-dashboard/Build_a_dashboard_30_nvpyix.webp" alt="Custom dashboard" title="Custom Dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we need to set these to alter each of the data providers. For the custom queries, we’ll click on the cog beside the &lt;em&gt;data&lt;/em&gt; dropdown and set the bindings to our form fields value:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g5Njdc9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_31_wj2jov.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g5Njdc9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674089/cms/build-a-dashboard/Build_a_dashboard_31_wj2jov.webp" alt="Front-end bindings" title="Front-end bindings" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repeat this step for all of our data providers.&lt;/p&gt;

&lt;p&gt;Then for our charts, we’ll need to use the &lt;em&gt;Define Filters&lt;/em&gt; menu to create equivalent filtering statements:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f2g4-9cl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_32_buedqp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f2g4-9cl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_32_buedqp.webp" alt="User defined filters" title="User defined filters" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can repeat this exact process for the Ad Spend tab, but simply leave out the &lt;em&gt;Item Name&lt;/em&gt; text field.&lt;/p&gt;

&lt;p&gt;Finally, we also need to make our data providers filterable by item, but only if the text field isn’t empty.&lt;/p&gt;

&lt;p&gt;For each data provider, we add a &lt;em&gt;condition&lt;/em&gt; to update the data to our other custom query that includes the &lt;em&gt;itemName&lt;/em&gt; binding, whenever the form field is not null:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---l0LLdnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_33_lrvibk.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---l0LLdnn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_33_lrvibk.webp" alt="Conditionality" title="conditionality" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the cog icon highlighted above to access the bindings menu and set the &lt;em&gt;itemName&lt;/em&gt; value to the form field input:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FFMF27bY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_34_s9qc7m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FFMF27bY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_34_s9qc7m.webp" alt="Filter by items" title="Filter by items" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And you can create a condition similar to this for the chart block’s data and filtering to finish it off.&lt;/p&gt;

&lt;p&gt;Now, we have all of our data displayed and filterable by user inputs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Adding realtime refreshes
&lt;/h4&gt;

&lt;p&gt;We’re just going to add one more little trick to our dashboard, using another custom component plug-in. This one is called the &lt;em&gt;interval&lt;/em&gt; component. &lt;/p&gt;

&lt;p&gt;This lets us define an interval in seconds, and an action to perform each time this is met.&lt;/p&gt;

&lt;p&gt;We’re setting ours to 120 seconds, so it goes off every two minutes, with an action to refresh any data providers it’s nested inside. We’ll eject our chart block to expose the data provider inside it.&lt;/p&gt;

&lt;p&gt;Then we’ll place an &lt;em&gt;interval component&lt;/em&gt; inside every data provider:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4MHwzseM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_35_mz1tur.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4MHwzseM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_35_mz1tur.webp" alt="real time dashboard" title="real time dashboard" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where we have data providers nested inside other data providers, we can nest an interval within the innermost one, and set multiple actions to refresh them all.&lt;/p&gt;

&lt;p&gt;Now our dashboard will display our desired data in real-time, with a two-minute refresh rate. So, we’ll never be dealing with out-of-date figures. All of the data providers will also refresh whenever the screen is reloaded or refreshed.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Design tweaks and UX
&lt;/h3&gt;

&lt;p&gt;Finally, we’ll tweak our design a little bit, just to make it a bit more appealing for users. First, we’ll set our theme to &lt;em&gt;Lightest&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F1lmiHgU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_36_tjdf3j.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F1lmiHgU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_36_tjdf3j.webp" alt="dashboard design" title="dashboard design" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then we’ll remove any navigational elements, the app logo, and the title, to give it a cleaner look:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UsalGMor--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_37_khqikg.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UsalGMor--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674086/cms/build-a-dashboard/Build_a_dashboard_37_khqikg.webp" alt="dashboard design" title="dashboard design" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And we’re done!&lt;/p&gt;

&lt;p&gt;Here’s our finished app again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7p1hCcz0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_1_tstu7m.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7p1hCcz0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://res.cloudinary.com/daog6scxm/image/upload/v1682674087/cms/build-a-dashboard/Build_a_dashboard_1_tstu7m.webp" alt="How to build a dashboard" title="how to build a dashboard" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To learn more, why not check out our ultimate guide to &lt;a href="https://budibase.com/blog/data/"&gt;data management software solutions&lt;/a&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn data into action with Budibase
&lt;/h2&gt;

&lt;p&gt;At Budibase, our mission is to help teams turn data into action. IT teams are under more pressure and businesses are more reliant on data-driven decision-making than ever before.&lt;/p&gt;

&lt;p&gt;Let’s check out how Budibase empowers busy IT professionals to do more with less.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our open-source, low-code platform
&lt;/h3&gt;

&lt;p&gt;With intuitive design tools, autogenerated screens, extensive data support, and immense extensibility, our platform is the fast, easy way to build all kinds of custom web apps.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://budibase.com/product"&gt;features overview&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect to external data
&lt;/h3&gt;

&lt;p&gt;We lead the pack for external data support. Budibase offers dedicated connector UIs for SQL, Postgres, Google Sheets, Airtable, REST, Oracle, S3, Mongo, Couch, and more. Wherever your data lives, Budibase is the easy way to build professional UIs around it.&lt;/p&gt;

&lt;p&gt;We also have our own built-in database with full support for CSV uploads, as well as an internal API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build automations
&lt;/h3&gt;

&lt;p&gt;Build custom automation rules with minimal manual coding. Our built-in automation editor features an array of nestable, configurable actions and triggers, with support for looping and conditional logic.&lt;/p&gt;

&lt;p&gt;You can even leverage external data and events within automation rules, using Zapier, webhooks, and REST.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional self-hosting
&lt;/h3&gt;

&lt;p&gt;Budibase offers optional-self hosting. Deploy to your own infrastructure, using Kubernetes, Docker, Docker Compose, Digital Ocean, and more.&lt;/p&gt;

&lt;p&gt;Or, choose Budibase Cloud, and let us handle everything. Check out our &lt;a href="https://budibase.com/pricing/"&gt;pricing page&lt;/a&gt; to learn more about both options. &lt;/p&gt;

&lt;h3&gt;
  
  
  Custom plug-ins
&lt;/h3&gt;

&lt;p&gt;At Budibase, our design philosophy is &lt;em&gt;simplicity by default; extensibility when you need it&lt;/em&gt;. So, we launched custom plug-ins. Build your own data sources and components, and ship them across all of your Budibase builds.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://docs.budibase.com/docs/custom-plugin"&gt;plug-ins documentation&lt;/a&gt; to learn more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configurable RBAC
&lt;/h3&gt;

&lt;p&gt;Budibase makes it a breeze to perfectly balance security with user experience. Assign users to preconfigured roles and grant or restrict access at the level of data sources, queries, automation rules, screens, or individual components.&lt;/p&gt;

&lt;p&gt;We also offer free SSO and user groups, so you can manage access however you need.&lt;/p&gt;

&lt;h3&gt;
  
  
  50+ free app templates
&lt;/h3&gt;

&lt;p&gt;Budibase is the ideal solution for building all kinds of internal tools. To prove it, we’ve created more than fifty free, deployment-ready, and fully customizable &lt;a href="https://budibase.com/templates"&gt;app templates&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To start building web apps the fast, easy way, sign up to Budibase today for free.&lt;/p&gt;

</description>
      <category>dashboard</category>
      <category>data</category>
      <category>tutorial</category>
      <category>lowcode</category>
    </item>
    <item>
      <title>Build a Postgres GUI in 3 Steps</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Fri, 18 Nov 2022 09:11:24 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/build-a-postgres-gui-in-3-steps-1a06</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/build-a-postgres-gui-in-3-steps-1a06</guid>
      <description>&lt;p&gt;A Postgres GUI helps you make the most out of your database, whether you are a coder or not.&lt;/p&gt;

&lt;p&gt;Databases are incredible tools for businesses. They allow you to store and process data at scale so that you can make better-informed decisions.&lt;/p&gt;

&lt;p&gt;You have two options to load data from databases. You can use code, via a command line or a programming language. So, you type in the commands in a terminal or send your code to a server and you get the results.&lt;/p&gt;

&lt;p&gt;Or you can use a Graphical User Interface (GUI) to visually interact with your database. With a GUI you interact with your database using screens, buttons, and visual elements to see what is happening.&lt;/p&gt;

&lt;p&gt;Thus, a GUI allows you to access your database, even if you aren’t a coder. If you are a coder, it is still a great option to run simple tasks. You can always run your queries manually in case you need them either way.&lt;/p&gt;

&lt;p&gt;It is quite tricky to pick the right tool, though. There are many factors to consider, including licensing price, features, online access, number of users, ease of use, and many more.&lt;/p&gt;

&lt;p&gt;But there's a simple solution.&lt;/p&gt;

&lt;p&gt;You can build your own Postgres GUI using a free, &lt;a href="https://budibase.com/blog/open-source-low-code-platforms/" rel="noopener noreferrer"&gt;open-source low-code platform&lt;/a&gt; like Budibase.&lt;/p&gt;

&lt;p&gt;It's much easier than it sounds, it requires no coding knowledge and you can do it in three steps.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect to your Postgres Database.&lt;/li&gt;
&lt;li&gt;Auto-generate the GUI screens.&lt;/li&gt;
&lt;li&gt;Add your custom elements to your Postgres GUI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Today we walk you through this process. You’ll learn how you can connect to your database, get data from it, and generate screens automatically. Then, you’ll learn how to customize the app to add your own functions.&lt;/p&gt;

&lt;p&gt;All of that with no coding knowledge, using Budibase.&lt;/p&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Is there a GUI for Postgres?
&lt;/h2&gt;

&lt;p&gt;Postgres is a database system. It is a tool to store and process data.&lt;/p&gt;

&lt;p&gt;Thus, it doesn't have an official GUI.&lt;/p&gt;

&lt;p&gt;But there are many Postgres GUI options available. In addition, you can build your own GUI in just three steps.&lt;/p&gt;

&lt;p&gt;Let’s see what you can build in the next section.&lt;/p&gt;

&lt;p&gt;You might also like our guide to building a &lt;a href="https://dev.to/blog/tutorials/mysql-gui"&gt;MySQL GUI&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the best client for PostgreSQL?
&lt;/h2&gt;

&lt;p&gt;The best Postgres GUI depends on your requirements. There are pre-made tools with a wide range of features and prices, yet there’s no guarantee that you’ll find the perfect fit for your business.&lt;/p&gt;

&lt;p&gt;At the end of the day, the best PostgreSQL client is the one that does everything you need. And very rarely will you find this in a premade tool.&lt;/p&gt;

&lt;p&gt;Building an app with code can take a lot of time and it can be very expensive. But you can use a low-code builder to do it for free.&lt;/p&gt;

&lt;p&gt;Let's see what we consider the best PostgreSQL client if you just want to get started.&lt;/p&gt;

&lt;p&gt;The demo app is connected to a PostgreSQL database for a blog.&lt;/p&gt;

&lt;p&gt;There are two tables, one for posts and one for settings.&lt;/p&gt;

&lt;p&gt;The first screen you see when you load up the app is the posts table:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960800%2Fcms%2F01_johjiy.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960800%2Fcms%2F01_johjiy.webp" title="Postgres GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In it, you can see a drop-down to select which table you want to work with. This is a useful tool if you have many tables. It allows you to navigate between screens without adding a menu item for each.&lt;/p&gt;

&lt;p&gt;If you scroll down a little bit you can see the search toggle. When you click this button, you see the table fields and you can use it to search items, like this:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960815%2Fcms%2F02_utalgm.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960815%2Fcms%2F02_utalgm.webp" title="How to build a postgres GUI" alt="How to build a postgres GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you can see the table data. You can click on the "view" button or the add new button to manage items. These buttons open a modal screen with this form:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960839%2Fcms%2F03_ahdv2k.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960839%2Fcms%2F03_ahdv2k.webp" title="Edit Row" alt="Edit Row"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is just a form with fields coming from the table. But you can spice it up in any way you want to. You can include reference data, API calls, data from other tables, and even actions to send emails.&lt;/p&gt;

&lt;p&gt;The settings screen is very similar, but it uses different elements:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960855%2Fcms%2F04_tmdocc.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960855%2Fcms%2F04_tmdocc.webp" title="Postgres GUI" alt=" Postgres GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All elements are the same, but instead of the search toggle, there’s a dynamic filter. This is another option to filter table data, and you can see the pros and cons of each item when you are creating them.&lt;/p&gt;

&lt;p&gt;Let's build your Postgres GUI now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - How do I connect to PostgreSQL GUI?
&lt;/h2&gt;

&lt;p&gt;You can connect your Postgres GUI with your database with the host URL, database name, username, and password. Make sure you whitelist the Budibase IP address in case your server has a firewall.&lt;/p&gt;

&lt;p&gt;To build your app, head over to Budibase and create an account if you don't have one. Then create a new app and use PostgreSQL as your data source.&lt;/p&gt;

&lt;p&gt;Add your credentials, fetch your tables, and you are ready to use them on Budibase:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960936%2Fcms%2F05_yuls2e.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664960936%2Fcms%2F05_yuls2e.webp" title="Data tab" alt="Data tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You don’t need to create any simple CRUD queries. Budibases does them for you automatically.&lt;/p&gt;

&lt;p&gt;Now you can create a query to get a list of the tables in your database. It allows you to automatically add items to the tables selector dropdown, so you don’t need to manually add them to that component.&lt;/p&gt;

&lt;p&gt;While still with the PostgreSQL data source selected, click on add query and use these options:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961049%2Fcms%2F06_qhx4zb.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961049%2Fcms%2F06_qhx4zb.webp" title="Postgres query" alt="Postgres query"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just replace the username in this query with your own username. Run it and save it.&lt;/p&gt;

&lt;p&gt;Next, let's follow the mandatory step in any app design and use a dark theme.&lt;/p&gt;

&lt;p&gt;Head over to the design tab, pick the theme menu option, and use the theme options.&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961079%2Fcms%2F07_hkr72y.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961079%2Fcms%2F07_hkr72y.webp" title="Theme Option" alt="Them option"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition to the overall app theme, you can edit the app header/navigation. Click on the navigation section to bring the options to edit the menu items along with the header colors:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961283%2Fcms%2F08_l2uyss.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961283%2Fcms%2F08_l2uyss.webp" title="Postgres GUI" alt="Postgres GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can pick any CSS color you want, from a simple hex value to CSS gradients.&lt;/p&gt;

&lt;p&gt;Now let’s create your app screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Autogenerate Postgres GUI screens
&lt;/h2&gt;

&lt;p&gt;Head over to the “screens” section. That’s where you can create each of your app routes:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961303%2Fcms%2F09_aakdbq.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961303%2Fcms%2F09_aakdbq.webp" title="Screens" alt="Screens"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on “&lt;em&gt;add screen”,&lt;/em&gt; and select “&lt;em&gt;autogenerated screens”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This option automatically creates CRUD screens for one of your tables. Select the posts table and then repeat this process for the setting stable.&lt;/p&gt;

&lt;p&gt;At the end of this process, you have six screens, three for each of your tables.&lt;/p&gt;

&lt;p&gt;This is what each of these screens do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/tablename - this screen shows you the database table, a button to add new items and edit buttons on each of the table rows.&lt;/li&gt;
&lt;li&gt;/tablename/new/row - this screen contains a form to add new items, and it uses fields from the table schema.&lt;/li&gt;
&lt;li&gt;/tablename/:id - this screen uses a route with a variable :id. So, anything that goes after the /tablename/ portion of your URL is saved in the :id variable. This allows you to prepopulate the edit form with data from the item you want to edit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a simple Postgres GUI, that is all you need to do.&lt;/p&gt;

&lt;p&gt;The beauty of using autogenerated screens is that it gives you an immense headstart. And you can edit these screens if you want to add more options.&lt;/p&gt;

&lt;p&gt;For example, you might want to connect to an API when editing items and populate a field with it. Or maybe you want to add search options to quickly find a specific entry from your tables. You can edit these screens, instead of starting from scratch.&lt;/p&gt;

&lt;p&gt;Let’s see some customization examples next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - Customize the GUI screens
&lt;/h2&gt;

&lt;p&gt;There are four main customizations in the demo app, and they show you how to do many different tasks with Budibase.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add tables navigation with a dropdown.&lt;/li&gt;
&lt;li&gt;Add dynamic filters to a table.&lt;/li&gt;
&lt;li&gt;Add custom search filters to a table.&lt;/li&gt;
&lt;li&gt;Open the edit/new screens in modals.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is how you can do each of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to add a dropdown to navigate through Postgres tables
&lt;/h3&gt;

&lt;p&gt;The logic of this feature is to list all your database tables, then when you select one of them your app loads the /tablename screen.&lt;/p&gt;

&lt;p&gt;Edit the posts screen and add a new component to it using the blue button. You can use this structure:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961332%2Fcms%2F10_lbilzk.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961332%2Fcms%2F10_lbilzk.webp" title="Components" alt="Components"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The parent component for the selector is just a container. Inside of it, you can add a data provider. This provider loads the show_tables query. This provider is simply a data source for the actual options picker.&lt;/p&gt;

&lt;p&gt;Now add a form component inside the data provider, just to make sure that the options picker works. Add the options picker and select the data source as the data provider. Labels and values are the table name.&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961352%2Fcms%2F11_zelr3s.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961352%2Fcms%2F11_zelr3s.webp" title="Postgres GUI" alt="Postgres GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, add an onChange action for it, like this:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961373%2Fcms%2F12_l7jfq5.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961373%2Fcms%2F12_l7jfq5.webp" title="Button action" alt="Button action"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s all you need to use this dropdown as a quick way to navigate to all the /tablename routes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to add dynamic filters to Budibase tables
&lt;/h3&gt;

&lt;p&gt;Now, let’s edit the settings page and add a filtering component to it. Click on the data provider, add new component and select the dynamic filter.&lt;/p&gt;

&lt;p&gt;This component automatically changes the data provider, allowing user inputs on each of the table columns, like this:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961408%2Fcms%2F13_pbejfg.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961408%2Fcms%2F13_pbejfg.webp" title="Filtering" alt="Filtering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s a very quick way to add search options to your Postgres GUI. But since this is a premade component you can’t fully customize it. If you want a custom logic in your search, then the next component works better.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to create a custom search form
&lt;/h3&gt;

&lt;p&gt;Back to the posts page, add a form component above the table data provider. This form contains the search form itself. This is the final tree structure for that page so you can follow along:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961437%2Fcms%2F14_j4vr3h.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961437%2Fcms%2F14_j4vr3h.webp" title="Postgres GUI" alt="Postgres GUI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The search form is a parent of the data provider, so you can use the form inputs in the data provider itself.&lt;/p&gt;

&lt;p&gt;The “search items” button toggles the visibility of the fields group. You can do it with an app state.&lt;/p&gt;

&lt;p&gt;Set the onclick actions of this button to run “Update State”, to “set value” with the key “search”. In the value click on the binding button, and then on the JS code.&lt;/p&gt;

&lt;p&gt;Use this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if ( $("State.search") ) {
    return 0;
    } 
else {
    return 1;
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code just alternates between 0 and 1. If the current app state is zero (search fields hidden), it sets this state as one (fields visible). If it is one, the app state is updated to zero (hiding the fields).&lt;/p&gt;

&lt;p&gt;As for the form fields themselves, here is where you can go crazy. You can add anything you want there. For example, you can use referenced fields, but instead of displaying a client_id, you allow users to search by the client name or even their address.&lt;/p&gt;

&lt;p&gt;Right now, we just use two simple text fields, as that’s what we have in our table.&lt;/p&gt;

&lt;p&gt;Then, to make these fields affect your data provider, set its filters like this:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961458%2Fcms%2F15_irbnr4.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961458%2Fcms%2F15_irbnr4.webp" title="Bindings" alt="Bindings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where you can set anything you want, from bindings to JS code.&lt;/p&gt;

&lt;p&gt;That’s it, your custom search is ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to open and close modal screens in Budibase
&lt;/h3&gt;

&lt;p&gt;Lastly, you can make the &lt;em&gt;edit&lt;/em&gt; and &lt;em&gt;add new&lt;/em&gt; buttons to open in a modal screen, instead of reloading the app.&lt;/p&gt;

&lt;p&gt;You can do it by editing the original table item, removing the default link, and using a button instead. Then, use this onClick action for the edit button:&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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961479%2Fcms%2F16_sbc3ni.webp" 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%2Fres.cloudinary.com%2Fdaog6scxm%2Fimage%2Fupload%2Fv1664961479%2Fcms%2F16_sbc3ni.webp" title="Buttons" alt="Buttons"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can make a very similar edit to the &lt;em&gt;create new&lt;/em&gt; button, just check the “open in a modal’ option.&lt;/p&gt;

&lt;p&gt;This is enough to make the forms open in a modal. But you need to close it.&lt;/p&gt;

&lt;p&gt;Edit the delete, and save buttons (from the edit and add new screens). Instead of “navigate to” actions, use “close modal” actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a Postgres GUI in Budibase
&lt;/h2&gt;

&lt;p&gt;Today you learned how you can create a Postgres GUI to interact with your database in just 3 steps. Now you can quickly deploy your own GUI, and customize it in any way you want to.&lt;/p&gt;

&lt;p&gt;Check out our in-depth guide to building &lt;a href="https://budibase.com/blog/tutorials/database-gui/" rel="noopener noreferrer"&gt;database GUIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We hope you enjoyed it, and see you again next time!&lt;/p&gt;

</description>
      <category>lowcode</category>
      <category>postgres</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Create a REST API GUI with Budibase</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Wed, 16 Nov 2022 09:21:53 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/create-a-rest-api-gui-with-budibase-34o1</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/create-a-rest-api-gui-with-budibase-34o1</guid>
      <description>&lt;p&gt;If you need a simple and flexible REST API GUI, you’ve come to the right place.&lt;/p&gt;

&lt;p&gt;REST APIs are incredibly useful. They allow you to interact with services and sites without scraping them. You can just access a URL, send some parameters and get a response.&lt;/p&gt;

&lt;p&gt;But when it comes to building your apps and actually communicating with them, things can get tricky. Sometimes they won’t respond as you’d expect. It can get quite tricky to pinpoint where the issue is- with your code or the API itself.&lt;/p&gt;

&lt;p&gt;Thus, a REST API GUI helps you to identify what exactly is going on. You have visual elements to control what information is being sent, so you can read the response.&lt;/p&gt;

&lt;p&gt;There are quite a few options for REST API GUIs though. But if you need something simple yet powerful, often building your own GUI is the best option.&lt;/p&gt;

&lt;p&gt;And you can do it in just 4 steps.&lt;/p&gt;

&lt;p&gt;This might seem like overkill, but building your own REST API GUI is, in most cases, faster than picking a premade tool and learning it. With the added bonus that, without coding, you can build a tool that perfectly fits your needs.&lt;/p&gt;

&lt;p&gt;This is what you are going to learn today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A one-step method to import and test endpoints for any REST API.&lt;/li&gt;
&lt;li&gt;How to create an app without coding.&lt;/li&gt;
&lt;li&gt;How to connect this app to a REST API.&lt;/li&gt;
&lt;li&gt;How to build screens to send requests and output information.&lt;/li&gt;
&lt;li&gt;How to save requests for later use.&lt;/li&gt;
&lt;li&gt;How to build CRUD screens with one click.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a REST API interface?
&lt;/h2&gt;

&lt;p&gt;A REST API allows you to use HTTP requests to GET (read), PUT (update), POST (create) and DELETE data. Thus, it’s similar to performing CRUD operations, but instead of connecting to a database, you are connecting to an external service using URL calls.&lt;/p&gt;

&lt;p&gt;This is one of the most fundamental techniques that developers use to connect web apps to different &lt;a href="https://budibase.com/blog/data/data-sources/"&gt;data sources.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An interface allows you to perform all these operations visually, with a tool such as a REST API GUI. Thus, you don’t need to rely solely on command lines or programming languages to test or perform your operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are all APIs RESTful?
&lt;/h2&gt;

&lt;p&gt;There are strict requirements for RESTful APIs. REST refers to the set of rules and the architecture of your API. Thus, if your API uses a different architecture it isn’t RESTful.&lt;/p&gt;

&lt;p&gt;Essentially, an architecture is a defined framework for sending and requesting data.&lt;/p&gt;

&lt;p&gt;APIs can use other architectures (such as SOAP or GraphQL) or they can be using their own architecture.&lt;/p&gt;

&lt;p&gt;This is important because most clients and applications that connect to any RESTful API rely on these standards. It is like manufacturing a data transfer cable. If you pick USB-c, you need to use a physical format and transfer data with specific protocols. Otherwise, you can’t connect to other devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I test REST API calls?
&lt;/h2&gt;

&lt;p&gt;You can test REST API calls using Budibase.&lt;/p&gt;

&lt;p&gt;The first and simplest method is to create a free account, create a new app, then pick a REST API data source.&lt;/p&gt;

&lt;p&gt;Next, just import your API specs using this option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ua4qWCYx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665483891/cms/01_xapbbq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ua4qWCYx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665483891/cms/01_xapbbq.webp" alt="REST API GUI" title="REST API GUI" width="880" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then you can use the data source tab itself to quickly test your API calls and see the response.&lt;/p&gt;

&lt;p&gt;Another way to do it is using a purpose-built REST API GUI. It might sound like a lot of work, but you can do it in just 4 steps with our intuitive low-code platform.&lt;/p&gt;

&lt;p&gt;At the end of this process, you’ll get an app with these screens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All Queries - List all saved queries

&lt;ul&gt;
&lt;li&gt;Edit/add new&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;New POST - Run new POST calls&lt;/li&gt;
&lt;li&gt;New GET - Run new GET calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how the “all queries” screen looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AbQ0mzrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484000/cms/02_dg4urj.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AbQ0mzrP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484000/cms/02_dg4urj.webp" alt="Queries screen" title="Queries screen" width="880" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see the main menu at the top. Then there’s a table with the currently saved queries, along with their method, a “run query” button, and an edit button. In addition, there’s an option to add new queries there.&lt;/p&gt;

&lt;p&gt;Both the edit and add new screens load similar forms, like this one:&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2HvH3GN_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484021/cms/03_b5q4lp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2HvH3GN_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484021/cms/03_b5q4lp.webp" alt="REST API GUI" title="REST API GUI" width="880" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a form where you can edit the query, or delete it. If you click on “run query” on the previous screen, the post or get screen is loaded, depending on the query you are running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hiC-Nae6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484040/cms/04_etzaoe.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hiC-Nae6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484040/cms/04_etzaoe.webp" alt="POST Requests" title="POST Requests" width="880" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the POST screen. In it, you can pass the route, and your data and you’ll see the query results. In addition, you can save queries if you want to.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AVfbP0s---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484100/cms/05_blgdsb.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AVfbP0s---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484100/cms/05_blgdsb.webp" alt="GET Request" title="GET Request" width="880" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the GET screen. It is very similar to the previous one, but it doesn’t require a request body. It allows you to run GET queries and see the results as well.&lt;/p&gt;

&lt;p&gt;Now let’s see how you can implement this app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 - Create your app
&lt;/h2&gt;

&lt;p&gt;If you haven’t already, sign up for Budibase. It’s free and it takes just a couple of seconds.&lt;/p&gt;

&lt;p&gt;Then, create a new app, and add two data sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST API&lt;/li&gt;
&lt;li&gt;BudibaseDB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use the Budibase DB to quickly store metadata about your app. In our demo, we use it to store the saved queries. You could use it to manage users, add settings, or anything you want.&lt;/p&gt;

&lt;p&gt;Create a new table called rest_queries. Then add columns for URL, JSON Body, and Method.&lt;/p&gt;

&lt;p&gt;Next, on the REST API settings let’s add some queries. In our demo app, we are connecting to the &lt;a href="https://docs.budibase.com/docs/public-api"&gt;Budibase API&lt;/a&gt;. It allows us to manage data about apps and users.&lt;/p&gt;

&lt;p&gt;Two headers are used in all queries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content-Type - application/JSON.&lt;/li&gt;
&lt;li&gt;X-budibase-api-key - your API Key. You can find it on the main Budibase page and click on your profile icon:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---0x9rbSZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484179/cms/06_l4rxyr.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---0x9rbSZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484179/cms/06_l4rxyr.webp" alt="REST API Key" title="REST API Key" width="880" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since these two headers are global, you can just add them on the main Data Source page. This loads them in all of your queries.&lt;/p&gt;

&lt;p&gt;Now let’s create our queries. Since the actual query data is added on the front end, by your users, we just need one query for each of the methods you use. In our demo we just use GET and POST, so we need two queries.&lt;/p&gt;

&lt;p&gt;If you have other methods in your app, you can add more queries there.&lt;/p&gt;

&lt;p&gt;To make these queries flexible, you need to rely on bindings. With bindings, the query data is replaced with whatever your screens use when loading that query.&lt;/p&gt;

&lt;p&gt;This is what the POST query looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0f_sT687--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484201/cms/07_cjgaba.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0f_sT687--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484201/cms/07_cjgaba.webp" alt="POST Query" title="POST Query" width="880" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how you are using a URL binding and a JSON body binding. Also, the actual request URL looks like this: &lt;code&gt;{{ url }}#post&lt;/code&gt;. Thus, if you add example.com/, the processed URL is example.com/#post&lt;/p&gt;

&lt;p&gt;This is just a small quality-of-life change so that you know which query you are working with on your screens. You could use different binding names for each if you want to. For example, urlPOST, and urlGET.&lt;/p&gt;

&lt;p&gt;Don’t forget to use the JSON body binding on the “Body” tab for the post request. Set the body as raw (JSON) and use this code:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{{ Binding.JSON }}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Also in this particular API, the return data is inside a “data” variable. You can make your life easier by using this transformer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;return data["data"]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With it, you can use the API output directly in your components such as tables and repeaters.&lt;/p&gt;

&lt;p&gt;Use the send button to test the call and save it.&lt;/p&gt;

&lt;p&gt;The default call is search apps, using the &lt;a href="https://budibase.app/api/public/v1/applications/search"&gt;https://budibase.app/api/public/v1/applications/search&lt;/a&gt; route.&lt;/p&gt;

&lt;p&gt;Once this call is working, copy it, and replace &lt;em&gt;POST&lt;/em&gt; with &lt;em&gt;GET&lt;/em&gt;. Remove the JSON binding and the JSON body. You should get something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KRWnBMN2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484234/cms/08_t0xhnd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KRWnBMN2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484234/cms/08_t0xhnd.webp" alt="GET request" title="GET Request" width="880" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, we are testing it with this route:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://budibase.app/api/public/v1/applications/%5C%5Badd_id%5C%5D"&gt;https://budibase.app/api/public/v1/applications/\[add_id\]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a GET request to load a specific app. Don’t forget to replace [app_id] with a valid app id so you can test it out. Save this query.&lt;/p&gt;

&lt;p&gt;You can edit other global settings for your app as well before building your screens. For example, you can head over to Design &amp;gt; Theme and pick a dark theme, or add a background color to your header.&lt;/p&gt;

&lt;p&gt;Now let’s create your app screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Create the query management pages
&lt;/h2&gt;

&lt;p&gt;The core goal of your REST API GUI app is to run custom queries and save them. But creating a screen to load saved queries is incredibly simple.&lt;/p&gt;

&lt;p&gt;Just go to Design &amp;gt; Screens &amp;gt; Add Screen. Select the “Autogenerated screens” option. Pick the rest_queries table.&lt;/p&gt;

&lt;p&gt;You’ll get three news screens when you pick this option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rest_queries - This screen loads a table so you can see your queries along with a “view” button&lt;/li&gt;
&lt;li&gt;rest_queries/:id - When you click on “view” you can see this form, to edit data or delete an item&lt;/li&gt;
&lt;li&gt;rest_queries/new/row - This is quite similar to the previous form, but to create new items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You will notice that the rest_queries screen is a bit different from the demo app. You can edit this screen in any way you want to better suit your REST API GUI.&lt;/p&gt;

&lt;p&gt;Now compare the element tree you have right now, with the elements tree from the demo app:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hX2J4Q1r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484264/cms/09_ajxczm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hX2J4Q1r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484264/cms/09_ajxczm.webp" alt="REST API GUI" title="REST API GUI" width="496" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main difference is in the rest_queries Table.&lt;/p&gt;

&lt;p&gt;First, there’s the “Run query” button. With the table selected, create a new button component. Then open up its “onclick” actions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7fHWClf9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484282/cms/10_eammu6.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7fHWClf9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484282/cms/10_eammu6.webp" alt="Button Actions" title="Button Actions" width="880" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This button has three actions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update State:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set value&lt;/li&gt;
&lt;li&gt;Key: url&lt;/li&gt;
&lt;li&gt;Value: &lt;code&gt;{{ rest_queries Table.rest_queries.URL }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Persist value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Update State:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set value&lt;/li&gt;
&lt;li&gt;Key: json&lt;/li&gt;
&lt;li&gt;Value: &lt;code&gt;{{ rest_queries Table.rest_queries.JSON Body }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Persist value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Navigate to page:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL: &lt;code&gt;/{{ rest_queries Table.rest_queries.method }}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these actions, when you click on “run query”, you are going to save two variables, and then navigate to /post or /get, depending on the current row.&lt;/p&gt;

&lt;p&gt;If you want, you can use different variables for urlget and urlpost. That would prevent the app from loading a GET URL when you visit the POST page and vice versa. In this case, you’ll need a different onclick logic using conditionals, which we won’t cover here.&lt;/p&gt;

&lt;p&gt;Moving on, the edit link is just the view link with a different name. Feel free to edit that component if you want.&lt;/p&gt;

&lt;p&gt;Lastly, there are the “post” and “get” tags. You can create them by adding both tag components to all rows. Then use the display conditions to show only the correct tag, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xxU7zNjM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484465/cms/11_zgreuv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xxU7zNjM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484465/cms/11_zgreuv.webp" alt="REST API GUI" title="REST API GUI" width="880" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use these conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POST: Show if &lt;code&gt;{{ rest_queries Table.rest_queries.method }}&lt;/code&gt; equals to post&lt;/li&gt;
&lt;li&gt;GET: Show if &lt;code&gt;{{ rest_queries Table.rest_queries.method }}&lt;/code&gt; equals to get&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s all for the home screen. Let’s create the POST calls screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 - POST calls screen
&lt;/h2&gt;

&lt;p&gt;Create a new screen with the /post route. The overall logic for this page is to load the &lt;code&gt;{{ url }}#post&lt;/code&gt; query using bindings from the variables. This allows you to load data when users click on the saved queries table. But it allows you to set this variable on that page as well.&lt;/p&gt;

&lt;p&gt;This is the elements tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uFdgoyPq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484484/cms/12_shrqvp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uFdgoyPq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484484/cms/12_shrqvp.webp" alt="Component Tree" title="Component Tree" width="494" height="1040"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create the title container with a title component. There are no big surprises on these components, just use some text to show a title on your page.&lt;/p&gt;

&lt;p&gt;Then, create a new form component. All form items need a form component as their parent. You can use a custom schema, then add the form fields components.&lt;/p&gt;

&lt;p&gt;The text input for the URL is a simple text field, like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7Up4rGnu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484651/cms/13_r3rfkh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7Up4rGnu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484651/cms/13_r3rfkh.webp" alt="REST API GUI" title="REST API GUI" width="500" height="1026"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how we load the default value from the &lt;code&gt;{{ State.url }}&lt;/code&gt; data. This pre-populates this field with whatever URL you have saved in your app, coming from this page or the homepage.&lt;/p&gt;

&lt;p&gt;Moving on, there’s the JSON Body multi-line component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rAncJVnR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484683/cms/14_lwamdm.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rAncJVnR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484683/cms/14_lwamdm.webp" alt="JSON Body" title="JSON Body" width="502" height="1026"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this component, you can steer users on how they should add their data. It is done using the &lt;code&gt;{&lt;/code&gt; bracket on the label, and a placeholder with just the JSON properties.&lt;/p&gt;

&lt;p&gt;Then add a paragraph to close the bracket &lt;code&gt;}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The “run” and “save” buttons are inside a container with these properties so that they are in a single line:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--quaquUdW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484701/cms/15_z3pypi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--quaquUdW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484701/cms/15_z3pypi.webp" alt="Horizontal Container" title="Horizontal Container" width="492" height="988"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The “run” button is the exact same button you have inside the rest_queries table. But it won't navigate to a page, and the bindings are based on the form components, not the table components. So:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update state &amp;gt; Set value &amp;gt; url: &lt;code&gt;{{ request data.Fields.URL }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Update state &amp;gt; Set value &amp;gt; json: &lt;code&gt;{{ request data.Fields.JSON Body }}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The “save” button runs the “Save row” action with these options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Datasource: none&lt;/li&gt;
&lt;li&gt;Table: rest_queries&lt;/li&gt;
&lt;li&gt;URL: &lt;code&gt;{{ request data.Fields.URL }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;JSON Body: &lt;code&gt;{{ request data.Fields.JSON Body }}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Method: post&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you just need to display your data.&lt;/p&gt;

&lt;p&gt;Create the component for the query result container and a data provider. You can use these bindings in it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1S1KEhti--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484772/cms/16_blz5ty.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1S1KEhti--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484772/cms/16_blz5ty.webp" alt="REST API GUI" title="REST API GUI" width="880" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Notice how the #post in the query name is useful now. Otherwise, you’d have two queries with the same name.&lt;/p&gt;

&lt;p&gt;Then pass the bindings from the app state, this allows the data source to always load whatever your REST API GUI is telling it to.&lt;/p&gt;

&lt;p&gt;Once you load data using a data provider, it is loaded as an array. You can access this data with a couple of different methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Directly, using bindings or JS code.&lt;/li&gt;
&lt;li&gt;Using a repeater.&lt;/li&gt;
&lt;li&gt;Using a table (like we did in the rest_queries CRUD screen).&lt;/li&gt;
&lt;li&gt;Using cards.&lt;/li&gt;
&lt;li&gt;Using charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A table isn’t the best option, since you don’t have a fixed schema. Each request can have a different number of columns with different data types.&lt;/p&gt;

&lt;p&gt;Cards have preset fields. They work well in just a few fields, but we need more.&lt;/p&gt;

&lt;p&gt;Thus, in this case, you can use a repeater and create your own output. Create a repeater and use the horizontal direction, just like you did for the buttons container.&lt;/p&gt;

&lt;p&gt;Then create the cards container to hold the output. You can set it with width: 30% in case you want items to side by side. You can set borders and other styling options as well if you want.&lt;/p&gt;

&lt;p&gt;The paragraph component contains this JS code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   var row = $("New Repeater.Row Index");
    row = $("Main Query.Rows")[row];
    var ret = "";
    Object.entries(row).forEach(([key, val]) =&amp;gt; {
    ret += "" + key + ":\n";
    ret += JSON.stringify(val, null, "\t");
    ret += "\n\n";
    });
    return ret;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function gets the current row index (a number), then loads a variable from the data provider array with the entire row. Next, for each of the row properties, you display the key and a string representation of the value (in case you have elements inside an element).&lt;/p&gt;

&lt;p&gt;That’s it, the POST screen of your REST API GUI is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 - GET calls screen
&lt;/h2&gt;

&lt;p&gt;You can duplicate the POST screen, and use the /get route for the new page.&lt;/p&gt;

&lt;p&gt;It is quite similar to the previous screen, this is its elements tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b_frKy8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484806/cms/17_iyrjll.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b_frKy8j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1665484806/cms/17_iyrjll.webp" alt="Component Tree" title="Component Tree" width="488" height="922"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You just need to update a few items on this page:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the data provider, from #post to #get.&lt;/li&gt;
&lt;li&gt;Remove the JSON body field.&lt;/li&gt;
&lt;li&gt;Update the “Save” button, so it sends an empty JSON body and the “get” method.&lt;/li&gt;
&lt;li&gt;Update the page title.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it!&lt;/p&gt;

&lt;p&gt;You can follow this method and create other screens if you want. You can have all the REST methods by just copying these base screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a REST API GUI with Budibase
&lt;/h2&gt;

&lt;p&gt;Today we looked into how you can create your own REST API GUI in just 4 steps. You saw how you can use Budibase to import and test REST API calls. Then you learned how you can create an app, and connect to a REST API using flexible calls based on user inputs.&lt;/p&gt;

&lt;p&gt;Lastly, you saw how you can bring it all together on screens to save queries, run them and use different REST API screens for each method.&lt;/p&gt;

&lt;p&gt;We hope you enjoyed it, and see you again next time!&lt;/p&gt;

&lt;p&gt;Check out our in-depth guide to building &lt;a href="https://budibase.com/blog/tutorials/database-gui/"&gt;database GUIs&lt;/a&gt; for more inspiration.&lt;/p&gt;

</description>
      <category>lowcode</category>
      <category>rest</category>
      <category>gui</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Build a SQL GUI in 5 Steps with Budibase</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Fri, 11 Nov 2022 14:42:17 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/build-a-sql-gui-in-5-steps-with-budibase-2789</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/build-a-sql-gui-in-5-steps-with-budibase-2789</guid>
      <description>&lt;p&gt;SQL is a powerful query language that can be used to unlock valuable insights stored in databases. However, it can be difficult to learn and use, particularly for non-specialists.&lt;/p&gt;

&lt;p&gt;That’s where a SQL GUI (Graphical User Interface) comes in handy.&lt;/p&gt;

&lt;p&gt;SQL GUIs are useful for developers and non-developers alike. They provide intuitive interfaces to interact with the database without writing commands. You can visually interact with tables, rows, fields, searches, and more, without any specialist knowledge.&lt;/p&gt;

&lt;p&gt;Thus, you don’t need to worry about the right code syntax to get the data you want to see. You just use the GUI components, like you would with any other app.&lt;/p&gt;

&lt;p&gt;They can cater to advanced users as well. With them, you can quickly perform simple tasks. And if you want to write SQL code anyway, it’s always possible to do it.&lt;/p&gt;

&lt;p&gt;Another benefit of a dedicated GUI is that you can have a single tool to load data from different databases with completely different architectures. Therefore, with a single tool, you can see all your MySQL, MariaDB, and even Oracle databases, wherever you are.&lt;/p&gt;

&lt;p&gt;The bad news is that buying a SQL GUI might not be an easy decision. There are many things to consider, such as licensing, access control, user volumes, app installs, and custom workflows.&lt;/p&gt;

&lt;p&gt;But you can solve this with low-code development.&lt;/p&gt;

&lt;p&gt;It’s possible to build your own SQL GUI in just 5 steps. Using Budibase, you can create a tool that does exactly what you want it to, that loads all your data, for unlimited users and with web access.&lt;/p&gt;

&lt;p&gt;This is the beauty of low-code development.&lt;/p&gt;

&lt;p&gt;The truth is, you don’t even need the full 5 steps. If you just want a simple CRUD interface, you can do it with just steps one and two. But today, we’re going to look at how to create something a little more sophisticated.&lt;/p&gt;

&lt;p&gt;Here is a quick overview of the steps to build your SQL GUI app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create your app and connect to your database.&lt;/li&gt;
&lt;li&gt;Create your CRUD interface with one click.&lt;/li&gt;
&lt;li&gt;Custom DB connections to read, write and delete data.&lt;/li&gt;
&lt;li&gt;Custom screens to see and filter all your tables.&lt;/li&gt;
&lt;li&gt;Forms to edit data from any table.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s get started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Does SQL have a GUI?
&lt;/h2&gt;

&lt;p&gt;SQL is a standard language that lets you store, manipulate and retrieve data in databases. It doesn’t have a GUI in itself, but there are many applications you can use for that.&lt;/p&gt;

&lt;p&gt;They come in a vast range of options and prices, and you can even build your own GUI for free, by following this tutorial.&lt;/p&gt;

&lt;p&gt;In case you want to check out what options there are out there, here is a list of off-the-shelf solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DBeaver.&lt;/li&gt;
&lt;li&gt;SQLyog.&lt;/li&gt;
&lt;li&gt;DataGrip by JetBrain.&lt;/li&gt;
&lt;li&gt;DbVisualizer.&lt;/li&gt;
&lt;li&gt;DronaHQ.&lt;/li&gt;
&lt;li&gt;MySQL Workbench.&lt;/li&gt;
&lt;li&gt;OmniDB.&lt;/li&gt;
&lt;li&gt;DataGrip.&lt;/li&gt;
&lt;li&gt;HeidiSQL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out our guide on how to &lt;a href="https://budibase.com/blog/data/create-forms-for-sql-databases/"&gt;create forms for SQL databases&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the best GUI for SQL?
&lt;/h2&gt;

&lt;p&gt;The best GUI depends on your needs. Some are more popular, due to how they are distributed. For example, phpMyAdmin is pretty ubiquitous.&lt;/p&gt;

&lt;p&gt;Others cater to very specific needs.&lt;/p&gt;

&lt;p&gt;Here are some points to consider when you pick a SQL GUI (or if you build your own):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;License cost&lt;/strong&gt; - be it one time, monthly or yearly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licensing limitations&lt;/strong&gt; - number of users, number of instances.&lt;/li&gt;
&lt;li&gt;*&lt;em&gt;Local or web-based *&lt;/em&gt;- some are accessible online, others only work as desktop installations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databases supported&lt;/strong&gt; - some work with a specific database, and others can work with multiple databases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ease of use&lt;/strong&gt; - how complex the interface is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customization&lt;/strong&gt; - if it’s possible to add new features to it via plugins, or even editing the source code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt; - if the tool works all the time or if it breaks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How do you create a GUI?
&lt;/h2&gt;

&lt;p&gt;You can create your SQL GUI using Budibase. There are actually two methods to interact with your databases, and you’ll learn both:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using the one-click interface to generate CRUD pages for a table.&lt;/li&gt;
&lt;li&gt;Using custom connections, queries, and a little bit of custom code to make a flexible interface that loads any table from a database connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The end result is something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gf8m6g1n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h0sm54znagm4372ng1nq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gf8m6g1n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h0sm54znagm4372ng1nq.png" alt="Build a SQL GUI" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the main app page. In it, you can select the tables, filter data, add new records, and edit rows. The add new/edit screen looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rkoD92qU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahtoqyitmfz3c98r5wls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rkoD92qU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahtoqyitmfz3c98r5wls.png" alt="Budibase screengrab" width="800" height="277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The form is the same for both the new row and edit options. The only difference is that one creates a new row, and the other updates a current row.&lt;/p&gt;

&lt;p&gt;In addition, there is the App Settings page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1AxL0Y9R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnbt8q14538j7g80sq0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1AxL0Y9R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnbt8q14538j7g80sq0b.png" alt="App settings page" width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The settings page is where you add your DB tables. Using this method your end users don’t need to edit the Budibase app to add new data. They can just insert them using this screen.&lt;/p&gt;

&lt;p&gt;If you take a closer look, it is quite similar to the home screen. The difference is that the settings screen is created using the auto-generated pages, while the home screen is custom-built.&lt;/p&gt;

&lt;p&gt;This may seem like a small difference in terms of end result, but they have different use cases and capabilities.&lt;/p&gt;

&lt;p&gt;It’s worth mentioning that we’re not going to dive too deep into security and user-control today. If you have a lot of users or critical data, make sure to include protections against SQL injection and proper user controls in your pages and queries.&lt;/p&gt;

&lt;h2&gt;
  
  
   1. Create your app and connect to a database
&lt;/h2&gt;

&lt;p&gt;To get started, you can create a free Budibase account.&lt;/p&gt;

&lt;p&gt;Once that’s done, it’s time to connect to your &lt;a href="https://budibase.com/blog/data/data-sources/"&gt;data source&lt;/a&gt;. In our demo, we are using a MySQL database.&lt;/p&gt;

&lt;p&gt;Add the connection details on the interface:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2fIw9CT9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5iu2wvlq5krdckqp7gm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2fIw9CT9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5iu2wvlq5krdckqp7gm.png" alt="Connection details" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure you have whitelisted the Budibase IP in your server. Otherwise, even with the right credentials, you won’t be able to connect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VIuMe85r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kz9x835wgf65jj9dwt4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VIuMe85r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kz9x835wgf65jj9dwt4y.png" alt="Whitelist IP" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next we need to store our App’s settings. You can use one of your tables to do it, but another option is to use Budibase DB.&lt;/p&gt;

&lt;p&gt;In our demo app we have a table called “_app_setting_s” with these columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Table_name.&lt;/li&gt;
&lt;li&gt;ID.&lt;/li&gt;
&lt;li&gt;Title.&lt;/li&gt;
&lt;li&gt;Value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ll use this table to select which columns from the MySQL tables correspond to this format. This allows us to use a single table with a fixed structure to display data from any table we want.&lt;/p&gt;

&lt;p&gt;For example, in the MySQL tables we have these tables and columns:&lt;/p&gt;

&lt;p&gt;Posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id,&lt;/li&gt;
&lt;li&gt;title,&lt;/li&gt;
&lt;li&gt;content,&lt;/li&gt;
&lt;li&gt;date,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id,&lt;/li&gt;
&lt;li&gt;settings_key,&lt;/li&gt;
&lt;li&gt;value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, we need to tell Budibase, which attributes from each of the tables go into each of the columns we have available.&lt;/p&gt;

&lt;p&gt;These are the mappings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C83sF-9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ts11lhiyfxs9a7xdyp8o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C83sF-9z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ts11lhiyfxs9a7xdyp8o.png" alt="Mappings" width="674" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Generate a SQL GUI automatically
&lt;/h2&gt;

&lt;p&gt;If you just need a simple GUI, you can create it automatically with Budibase. Let’s do this with the App Settings so you can use this method for your tables.&lt;/p&gt;

&lt;p&gt;Go to the Design tab, select the screens menu, and hit add screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RNiTdKJ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ok7zj1ec7ep5jkdxini.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RNiTdKJ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ok7zj1ec7ep5jkdxini.png" alt="Design tab" width="800" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then select the “Autogenerated screens” option and pick the app_settings table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jl2MzzfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vhyhmlabzzbr110q9eod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jl2MzzfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vhyhmlabzzbr110q9eod.png" alt="Autogenerated screens" width="800" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what you should see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FhW45HUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m72l7gd280334rlgqfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FhW45HUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m72l7gd280334rlgqfi.png" alt="Screens" width="730" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s all you need for a basic SQL GUI. You can repeat this process with all tables in case you want just these functions.&lt;/p&gt;

&lt;p&gt;In our example, we’ve changed the app formatting a bit too. You can do this in the “Theme” section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---t7JAM0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a70saxwqhbnnbq1h157.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---t7JAM0I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2a70saxwqhbnnbq1h157.png" alt="Theme" width="702" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We picked a dark theme and a CSS gradient for the header background. You can add any valid CSS background values in here, including simple colors or complex values, such as gradients and even background images.&lt;/p&gt;

&lt;p&gt;Now let’s see how you can spice up your SQL GUI with a single screen that loads multiple tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Custom database connections and queries
&lt;/h2&gt;

&lt;p&gt;If you have a lot of tables to manage, maybe manually creating a CRUD interface for each of them isn’t the best option. It’s possible to create your own interface that loads tables and databases automatically.&lt;/p&gt;

&lt;p&gt;For your end users, this might be an easier option, since they can add new tables and databases without editing the app itself.&lt;/p&gt;

&lt;p&gt;They just need to add the tables in a settings table. As long as your current DB connection has the right capabilities to access data, you’ll see it automatically.&lt;/p&gt;

&lt;p&gt;Now, as for how you can create this dynamic SQL GUI, there are a few important elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The DB commands&lt;/strong&gt; - they depend on your database, so you’ll need a screen for each of your database connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tables schema&lt;/strong&gt; - here we use a mapping method so all tables respond to the same schema. There are other ways of doing this though.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tricky part here is to take a user input (tables options) and add it to your queries dynamically. This is hard because SQL usually doesn’t allow functions or variables in table names.&lt;/p&gt;

&lt;p&gt;So you can’t just use a command like:&lt;br&gt;
&lt;code&gt;_SELECT * FROM {{ table }}_,&lt;/code&gt;&lt;br&gt;
Because this binding is treated like a plain string (wrapped by single quotes).&lt;/p&gt;

&lt;p&gt;Check out our guide to &lt;a href="https://budibase.com/blog/data/what-is-a-database-schema/"&gt;database schemas&lt;/a&gt; for more information.&lt;/p&gt;

&lt;p&gt;Some DBs such as MariaDB have a handy command for this, EXECUTE IMMEDIATE. It allows you to send a SQL command with variables or anything else you need.&lt;/p&gt;

&lt;p&gt;If your DB has this command, then you can just use something like this in your queries:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXECUTE IMMEDIATE ‘CONCAT ( ‘SELECT * from ‘, {{ table }} ) ’&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But since we are using MySQL we need something else. If you look closely, the EXECUTE IMMEDIATE command is actually just a shorthand for this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PREPARE stmt &lt;br&gt;
from ‘CONCAT ( 'SELECT * FROM ‘, {{ table }} ) ’;&lt;br&gt;
EXECUTE stmt;&lt;br&gt;
DEALLOCATE PREPARE stmt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And this is actually possible with MySQL. You just need access to your DB (using PHPMyAdmin or similar) and you can create a procedure there with something like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BEGIN&lt;br&gt;
SET @q = query;&lt;br&gt;
PREPARE stmt FROM @q;&lt;br&gt;
EXECUTE stmt;&lt;br&gt;
DEALLOCATE PREPARE stmt;&lt;br&gt;
END&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now you are ready to perform the same actions as you could do with MariaDB, with a slightly different syntax. This is how we do it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CALL execute_immediate( ‘CONCAT ( ‘SELECT * from ‘, {{ table }} ) ’ )&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s it, you are ready to run any type of command that you need using variables.&lt;/p&gt;

&lt;p&gt;Head over to the “Data” section, and let’s create three queries. One to select data, the second to edit/add data, and the last query to delete data.&lt;/p&gt;

&lt;p&gt;Here is the setup for the &lt;em&gt;variable_select&lt;/em&gt; query:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--b2mqxP8l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awtqfv4bmgrn7q8evu30.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--b2mqxP8l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/awtqfv4bmgrn7q8evu30.png" alt="Query" width="702" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then use this SQL command for the fields:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CALL execute_immediate( CONCAT ( ‘SELECT ‘, {{ID}}, ’ as “ID”, ‘, {{Title}}, ’ as “Title”, ‘, {{Value}}, ’ as “Value” FROM ‘, {{table}} ) )&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command takes the bindings and use them to build the query. Notice how everything is variable, including the table name and the column names, which are mapped to ID, Title, and Value.&lt;/p&gt;

&lt;p&gt;Also, notice that we are mapping the table columns to standard names, ID, Title, and Value. This allows us to use a single table and a single form to view and edit all of them.&lt;/p&gt;

&lt;p&gt;Make sure to use this transformer:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;return data[0]&lt;/code&gt;&lt;br&gt;
By default, when you execute a stored procedure, MySQL returns some extra data that you don’t really need. So you can just read the first value of the data array (data[0]) to get the query results.&lt;/p&gt;

&lt;p&gt;You can run this query and save it.&lt;/p&gt;

&lt;p&gt;Next, there’s the insert/edit query.&lt;/p&gt;

&lt;p&gt;This time, the entire command is dynamic, given that we use the form fields to create it.&lt;/p&gt;

&lt;p&gt;Here is the setup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7LFraavU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4c1k0ngwwiq743szjz1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7LFraavU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4c1k0ngwwiq743szjz1.png" alt="Setup" width="690" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly, there’s the delete query. Here is the setup for it:&lt;br&gt;
Custom SQL Query&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Eib-m17Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6nznnb0dbemadjhbwch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Eib-m17Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6nznnb0dbemadjhbwch.png" alt="Custom Query" width="308" height="554"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Load any table from your database
&lt;/h2&gt;

&lt;p&gt;If you want to load your tables dynamically, you need a few data points. Each of these can be pulled with the data provider component in Budibase.&lt;/p&gt;

&lt;p&gt;This should be the hierarchy of your data points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load all possible tables (from the Budibase DB app_settings table),&lt;/li&gt;
&lt;li&gt;Dropdown for the user to pick their desired table,&lt;/li&gt;
&lt;li&gt;Load the selected table’s mappings (ID, Value from app_settings)&lt;/li&gt;
&lt;li&gt;Load the table data sending the correct bindings (table name, id, value) to the variable_select MySQL query.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to implement this screen, you just need to follow that structure. This is your final element tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FmOUll38--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7stede9sw7fq7t1jjnii.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FmOUll38--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7stede9sw7fq7t1jjnii.png" alt="Component Tree" width="308" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first component is the main container. This is used to hold all of your other components.&lt;/p&gt;

&lt;p&gt;Then there’s the first App Settings Provider. It has no settings, and it just loads data from the app_settings table.&lt;/p&gt;

&lt;p&gt;Inside of that provider you’ll add the Select Table form. All form components require a form to be their parent, otherwise they won’t work. This form is there just to make the dropdown work, you don’t need to change its settings.&lt;/p&gt;

&lt;p&gt;Next there’s the table options picker. We are using these settings for it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H-akCjPU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a9dhb627zn2gyj4op42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H-akCjPU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7a9dhb627zn2gyj4op42.png" alt="Options picker" width="248" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When your user picks a new option, your table should change. You can do this by creating an app state. This is just a way to store variables temporarily in your app, based on user actions. To do that, use this option for the OnChange action of the button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xqBrqLa7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/waghzir2kfwyvubuzfc6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xqBrqLa7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/waghzir2kfwyvubuzfc6.png" alt="Button action" width="800" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In addition, we want that dropdown to automatically select the current table. But when users first load your app there will be no selection. Thus, you can select the default value of that field to be a JS function, like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var ret = $("State.selected_table");&lt;br&gt;
if ( ! ret ) {&lt;br&gt;
    ret = "posts";&lt;br&gt;
}&lt;br&gt;
return ret;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The next component to implement is the Selected Table Meta, which loads the metadata for the current user selection. It is a data provider just like the first one, loading data from the app_settings table.&lt;/p&gt;

&lt;p&gt;The difference is that now we add a filter to it. You can set it to load items with table_name equals &lt;code&gt;{{ State.selected_table }}.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then add a repeater to expose the fields of that data provider. And inside that repeater it’s finally time to load the table data.&lt;/p&gt;

&lt;p&gt;As the name suggests, the Selected Table Data Provider loads data for your selected table using the variable_select query. But you need to set the correct bindings to it to make sure it loads the current table’s data and maps it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qEjEJWqP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxprui8ygjqmehz7x3d8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qEjEJWqP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxprui8ygjqmehz7x3d8.png" alt="Table data" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can add a dynamic filter so users can view only what they want from that table.&lt;/p&gt;

&lt;p&gt;The next component is the “add new” button. It is a simple button that has a “navigate to” action when users click on it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CvV4gT9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zw95qgqgghol6zj0ljcs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CvV4gT9o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zw95qgqgghol6zj0ljcs.png" alt="Nav button" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can add your table inside the selected table data provider, and add a button inside of it to edit rows. Just like the add new button, the edit button simply navigates to another screen. But this time we use the current row as part of our URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R-334JGC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4g5mbui1yojs5u8346cg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R-334JGC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4g5mbui1yojs5u8346cg.png" alt="URL binding" width="800" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These URLs may look weird, but they are just a method to pass data to Budibase. In short, you are telling it to load the /edit/ screen with the row ID, or with zero, if it’s a new item. Then that screen handles these numbers as a variable.&lt;/p&gt;

&lt;p&gt;Let’s see how to do it in the next section.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Insert, edit, and delete data
&lt;/h2&gt;

&lt;p&gt;Just like the home screen, the edit screen requires some data points to work correctly.&lt;/p&gt;

&lt;p&gt;First, you need a URL variable to get which item is being updated. To do that, use /:id in your route. For example, our demo uses /edit/:id.&lt;/p&gt;

&lt;p&gt;Therefore, if you visit myapp/edit/130, then the id variable is set as 130. This works not just for numbers. If your app requires text-based variables, you can use them, for example, /edit/myitem. In this case, the id variable is myitem.&lt;/p&gt;

&lt;p&gt;It’s worth noting here that this will only work if the variable you bind to your URL is unique. We’d normally use ID, because this is an automatically generated attribute that’s unique to each individual row.&lt;/p&gt;

&lt;p&gt;In terms of data points, this is what you need for the edit screen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Item ID (URL),&lt;/li&gt;
&lt;li&gt;Current table (app state),&lt;/li&gt;
&lt;li&gt;Table mappings from app_settings,&lt;/li&gt;
&lt;li&gt;Add new item form inputs,&lt;/li&gt;
&lt;li&gt;Data provider to load the default values for the edit mode,&lt;/li&gt;
&lt;li&gt;Edit item form inputs,&lt;/li&gt;
&lt;li&gt;Item update state (for the success message).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can copy the following component tree:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kcg2Cn1U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr3iqx0t67rqqfjawksw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kcg2Cn1U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr3iqx0t67rqqfjawksw.png" alt="Component Tree" width="265" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Again, you have a main container to organize all your components.&lt;/p&gt;

&lt;p&gt;Then, inside of it there’s the App Settings Provider. It loads the app_settings table. This time, we are filtering it by table name equal to this function:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var state=$("State.selected_table");&lt;br&gt;
if ( state ) {&lt;br&gt;
return state&lt;br&gt;
} else {&lt;br&gt;
return "posts"&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This function returns either the currently selected table or the default table, which in our case is posts.&lt;/p&gt;

&lt;p&gt;The Add New container should only be visible if we are adding a new item. To do this, add a new container and set the conditions to Hide component if &lt;code&gt;{{ URL.id }}&lt;/code&gt; Not equals 0. So, if the url is anything other than /edit/0, we are editing an item.&lt;/p&gt;

&lt;p&gt;Therefore, the add new option shouldn’t be visible.&lt;/p&gt;

&lt;p&gt;The Add New form is just a placeholder for the components. This time, the entire action is accessed in the Add New button. You can insert the headline and text fields, then use this set of actions for the button:&lt;/p&gt;

&lt;p&gt;Update State - Set the app state updated_rows to zero (so the success message is hidden).&lt;/p&gt;

&lt;p&gt;Execute Query - the variable_update query, with settings for the “insert” mode and using the form values along with the bindings coming from the app_settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;table = INSERT INTO {{ State.selected_table }}
fields = (`{{ App Settings Provider.Rows.[0].[Title] }}`, `{{ App Settings Provider.Rows.[0].[Value] }}`) VALUES ( “{{ Add New Form.Fields.New Title }}”, “{{ Add New Form.Fields.New Value }}” )
Where = “”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important: Make sure to set the where as “ “, an empty space, so the default value won’t be used.&lt;/p&gt;

&lt;p&gt;Update State - Set the app state updated_rows to one to display the success message&lt;br&gt;
That’s all you need for the “add new” form. The edit form is quite similar, the main difference is that you have default values for the form items.&lt;/p&gt;

&lt;p&gt;Add a “view row” data provider. In it, you can load the variable_select query and use the bindings coming from the App Settings Provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;table = {{ App Settings Provider.Rows.[0].[table_name] }}
ID = {{ App Settings Provider.Rows.[0].[ID] }}
Title = {{ App Settings Provider.Rows.[0].[Title] }}
Value = {{ App Settings Provider.Rows.[0].[Value] }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition, set the conditions for the data provider to “Hide Component” if &lt;code&gt;{{ URL.id }}&lt;/code&gt; Equals zero. Therefore, if we are adding a new item (/edit/0), this entire block is hidden.&lt;/p&gt;

&lt;p&gt;Next, add a repeater to expose the data provider fields so they are easier to work with.&lt;/p&gt;

&lt;p&gt;Then, add a new button. This is the button to delete items. You can use this Custom CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;position: absolute;
right: 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can add three actions when that button is clicked:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Execute the variable delete - Make sure to require confirmation and use these bindings:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;table = {{ State.selected_table }}
ID = {{ App Settings Provider.Rows.[0].[ID] }}
IDValue = {{ New Repeater.variable_select.ID }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Update State updated_rows to zero, so the next time you load the edit page this message is hidden.&lt;/li&gt;
&lt;li&gt;Navigate to /home.
Then, you can add a headline with the value &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;{{ New Repeater.variable_select.Title }}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is the same value that you’ll use in the default value for the title text input. For the value text input, you can use &lt;code&gt;{{ New Repeater.variable_select.Value }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Like the previous form, the update form is just a placeholder. The actions are in the update button. The logic for this button is quite similar to the previous one. Here are the actions you’ll run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Update State updated_rows = 0
Execute Query variable_update:
table = UPDATE {{ State.selected_table }}
fields = SET {{ App Settings Provider.Rows.[0].[Title] }}="{{ Update Form.Fields.Title }}", {{ App Settings Provider.Rows.[0].[Value] }}="{{ Update Form.Fields.Value }}"
where = WHERE {{ App Settings Provider.Rows.[0].[ID] }}={{ New Repeater.variable_select.ID }}
Update State updated_rows = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it, your form is ready to edit items.&lt;/p&gt;

&lt;p&gt;The finishing touch is a message to let users know that the add new / update action worked. You can add a container and set its conditions to Show component if &lt;code&gt;{{ State.updated_rows }}&lt;/code&gt; Equals to 1.&lt;/p&gt;

&lt;p&gt;You can add a green border around it, and a headline component with your update message.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to build a SQL GUI
&lt;/h2&gt;

&lt;p&gt;We’ve only scratched the surface of what is possible with SQL GUI development, we hope you are now inspired to create your own.&lt;/p&gt;

&lt;p&gt;In this article, we have explored many different techniques, such as autogenerated screens, dynamic queries, different data loading methods.&lt;/p&gt;

&lt;p&gt;Only the sky is the limit when it comes to what you can do following the 5 steps from this tutorial.&lt;/p&gt;

&lt;p&gt;We hope you’ve enjoyed, and see you again next time!&lt;/p&gt;

&lt;p&gt;Check out our guide to building &lt;a href="http://blog/tutorials/database-gui"&gt;database GUIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To see more of what Budibase can do, why not check out our 50+ free, deployable &lt;a href="https://budibase.com/templates"&gt;app templates&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>gui</category>
      <category>lowcode</category>
      <category>beginners</category>
    </item>
    <item>
      <title>What is Data Access Control?</title>
      <dc:creator>Ronan McQuillan</dc:creator>
      <pubDate>Tue, 07 Jun 2022 09:15:02 +0000</pubDate>
      <link>https://dev.to/ronan_mcquillan_628bdb9e3/what-is-data-access-control-nb5</link>
      <guid>https://dev.to/ronan_mcquillan_628bdb9e3/what-is-data-access-control-nb5</guid>
      <description>&lt;p&gt;Data access control is a crucial element of any information security strategy.&lt;/p&gt;

&lt;p&gt;Companies today face a more complex regulatory environment than ever when it comes to protecting personal data. However, this must be balanced with the need for efficient, data-driven decision-making.&lt;/p&gt;

&lt;p&gt;As such, how users are assigned access to data across your organization has enormous implications.&lt;/p&gt;

&lt;p&gt;Today, we’re going to cover everything your need to know about data access controls, including the theory, the specific methods available to you, and how to create the right framework for your needs.&lt;/p&gt;

&lt;p&gt;First, though, let’s start with the basics.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is data access control?
&lt;/h2&gt;

&lt;p&gt;Data access control is any method you use to control how users interact with your company’s data. The goal is to ensure that data is accessed in a manner that meets your security, privacy, and compliance needs, without undermining efficiency or accessibility.&lt;/p&gt;

&lt;p&gt;Users can be employees or third parties, like customers, partners, consultants, or members of the public.&lt;/p&gt;

&lt;p&gt;In other words, data access control means defining and enforcing a system for how different users can use your organization's data.&lt;/p&gt;

&lt;p&gt;As we’ll see later, there are various types of access control.&lt;/p&gt;

&lt;p&gt;For now, though, let’s consider why this is such a critical issue for modern organizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why do you need data access control?
&lt;/h3&gt;

&lt;p&gt;Enterprises and SMBs alike store and process greater volumes of data than ever before. While the benefits of this are obvious, there are many other concerns to manage.&lt;/p&gt;

&lt;p&gt;The key is balancing the benefits of data-driven decision-making, including increased creativity, innovation, and productivity, with the need to maintain high standards across security, privacy, and compliance.&lt;/p&gt;

&lt;p&gt;This is where data access control comes into play.&lt;/p&gt;

&lt;p&gt;Here are four specific issues that data access control addresses.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SfcdG5hE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387543/cms/Security_il6bb4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SfcdG5hE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387543/cms/Security_il6bb4.png" alt="What is data access control - security" title="What is data access control - security" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Security
&lt;/h4&gt;

&lt;p&gt;The most obvious goal of access control is data security. In other words, ensuring that it is accessed only by the right people, in the right contexts. The idea is that only authorized entities can access data or carry out different actions on it.&lt;/p&gt;

&lt;p&gt;An entity here can mean a user, an automated process, or a particular platform.&lt;/p&gt;

&lt;p&gt;Of course, restricting access is just one weapon in your security arsenal. This sits alongside other tools including encryption, identity management, and hardware security solutions.&lt;/p&gt;

&lt;p&gt;Far from replacing these other techniques, data access control complements them by ensuring that an otherwise secure system isn’t circumvented, either deliberately or unintentionally.&lt;/p&gt;

&lt;p&gt;In fact, data access control is fundamental to any modern security strategy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QgdjjhUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387510/cms/Compliance_obxxoz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QgdjjhUE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387510/cms/Compliance_obxxoz.png" alt="Compliance" title="Compliance" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Compliance
&lt;/h4&gt;

&lt;p&gt;Privacy regulations are increasingly complex, especially for enterprises, or other companies that process personal data internationally. This places several constraints on how companies must treat data, especially where this concerns identifiable people.&lt;/p&gt;

&lt;p&gt;These constraints stem from established best practices, as well as formal regulations like GDPR, CCPA, HIPAA, PIPEDA, and NIST.&lt;/p&gt;

&lt;p&gt;While these all differ in terms of their specific content and the requirements placed on organizations, each one places limits on how subjects’ personal data can be accessed, and by whom.&lt;/p&gt;

&lt;p&gt;Most formal regulations stipulate in one way or another that only entities which need to access different data should be able to do so. Additionally, subjects’ data should only normally be accessed by the entities and for the reasons they initially consented to.&lt;/p&gt;

&lt;p&gt;As such, effective access control will inevitably form a core part of your compliance efforts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QOlYHfT4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387401/cms/Efficiency_tri6jk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QOlYHfT4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387401/cms/Efficiency_tri6jk.png" alt="Data access control efficiency" title="Data access control efficiency" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Efficiency
&lt;/h4&gt;

&lt;p&gt;Data access control is also an important tool for maximizing efficiency across different applications, workflows, and processes. A key part of this is limiting the number of actions that users can take.&lt;/p&gt;

&lt;p&gt;At the level of applications, this helps to ensure that user interfaces are as streamlined and effective as possible. At the database level, it helps to minimize labor costs stemming from unnecessary errors, security breaches, and administration tasks.&lt;/p&gt;

&lt;p&gt;Additionally, different access control methods offer extra efficiency savings, particularly in terms of assigning and administering permissions. Check out our ultimate guide to &lt;a href="https://budibase.com/blog/app-building/role-based-access-control/"&gt;role-based access control&lt;/a&gt; to find out more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mto92qfl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387372/cms/Validity_fbz48q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mto92qfl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387372/cms/Validity_fbz48q.png" alt="Data access control vailidity" title="Data access control validity" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Validity, accuracy &amp;amp; integrity
&lt;/h4&gt;

&lt;p&gt;Finally, data access control is an essential way to ensure validity, accuracy, and integrity in your company’s stored data. Essentially, by limiting the number of users who can perform certain actions on your data, you’re also reducing the risk of human error in doing so.&lt;/p&gt;

&lt;p&gt;For example, if only certain users have permission to perform &lt;em&gt;UPDATE&lt;/em&gt; or &lt;em&gt;INSERT&lt;/em&gt; queries, there’s a far smaller risk of input errors, leading to incorrect values.&lt;/p&gt;

&lt;p&gt;You might do this across entire datasets, or at the level of individual entries, tables, or views.&lt;/p&gt;

&lt;p&gt;In any case, the goal is to limit the number of entities that can make changes to your data, reserving these permissions to users and processes that strictly need to add or update values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access control methods
&lt;/h2&gt;

&lt;p&gt;Now that we have a firmer understanding of why data access control matters, we can think more concretely about how it actually works in practice. As ever, this depends on your specific needs, as well as the particular systems you’re using.&lt;/p&gt;

&lt;p&gt;It’s worth noting as well that different methods are aimed at different outcomes. Some are aimed at preventing specific threats, while others are more concerned with managing the day-to-day access requirements of your employees.&lt;/p&gt;

&lt;p&gt;As such, you’ll more than likely need to use a variety of methods in tandem.&lt;/p&gt;

&lt;p&gt;With that in mind, here are the broad categories of access control methods that are available to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entity-centric methods
&lt;/h3&gt;

&lt;p&gt;Entity-centric data access control involves setting permissions based on different entities’ attributes. Remember, an entity here can be a user, an automated process, or an external platform.&lt;/p&gt;

&lt;p&gt;Generally speaking, there are two ways to go about this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assigning entities a defined role, with set permissions.&lt;/li&gt;
&lt;li&gt;Creating conditions to assign permissions to entities, based on their existing attributes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are the two most common ways of assigning user permissions within applications. To find out more about these frameworks, check out our guide on &lt;a href="https://budibase.com/blog/app-building/difference-between-rule-based-role-based-access-control/"&gt;the differences between rule-based and role-based access control&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These models use existing organizational structures as the basis for determining permissions to different entities. For example, you might grant access to different employees based on their job title, department, or seniority.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data-centric methods
&lt;/h3&gt;

&lt;p&gt;Data-centric methods are centered around the type of data that’s being accessed. In other words, this involves setting rules to control access, at the level of the data itself, rather than based on entities.&lt;/p&gt;

&lt;p&gt;For example, you might have a particular dataset that can only be accessed via a dedicated application.&lt;/p&gt;

&lt;p&gt;This is a simple way to enforce uniform controls across all entities for a particular dataset.&lt;/p&gt;

&lt;p&gt;Of course, one side effect of this is that any user who can access the application then has complete access to the dataset, unless you put additional measures in place.&lt;/p&gt;

&lt;p&gt;We can work around this problem in a couple of ways. One would be to strictly control who can access the application, through authentication and identity management tools.&lt;/p&gt;

&lt;p&gt;Alternatively, we could &lt;a href="https://budibase.com/blog/app-building/how-to-implement-rbac/"&gt;implement RBAC&lt;/a&gt; within the application itself, to more tightly control which users can carry out different queries on the affected dataset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context-centric methods
&lt;/h3&gt;

&lt;p&gt;Finally, context-centric methods are based on the context of how data is accessed. The idea here is to use environmental factors to determine whether or not certain queries can be actioned.&lt;/p&gt;

&lt;p&gt;For example, you might restrict the ability to export large volumes of data to certain times of day, to prevent large-scale breaches.&lt;/p&gt;

&lt;p&gt;We could also restrict the ability to perform different actions on specific database objects, based on their current stage in a given workflow. For instance, we could create an &lt;a href="https://budibase.com/approval-apps"&gt;approval app&lt;/a&gt; for accessing different pieces of data.&lt;/p&gt;

&lt;p&gt;Rather than acting as the basis for your complete data access control system, context-centric methods are typically used to combat specific risks.&lt;/p&gt;

&lt;p&gt;Relying on context-centric methods requires you to identify and map known threats. As such, these techniques are unlikely to be sufficient for protecting your data, unless used in tandem with the other methods discussed above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access control, authentication, and authorization
&lt;/h2&gt;

&lt;p&gt;No discussion of data access would be complete without touching on the idea of authentication and authorization.&lt;/p&gt;

&lt;p&gt;Authentication is how you verify the identity of different entities.&lt;/p&gt;

&lt;p&gt;Authorization is whether or not an entity is allowed to access a particular piece of data or carry out a certain action. More specifically, we need to distinguish between authorized and unauthorized access.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A5ansDM7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387308/cms/Authorised_vs_unauthorised_r04hqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A5ansDM7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/daog6scxm/image/upload/v1653387308/cms/Authorised_vs_unauthorised_r04hqf.png" alt="Data access control authorized access" title="Data access control authorized access" width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Authorized vs unauthorized access
&lt;/h3&gt;

&lt;p&gt;The distinction between authorized and unauthorized access is effectively a matter of whether or not an entity has permission to carry out an action. Most often reading, creating, updating, or deleting one or more entries.&lt;/p&gt;

&lt;p&gt;As the name suggests, authorized access is when an entity carries out an action that they have permission to do.&lt;/p&gt;

&lt;p&gt;On the other hand, unauthorized access means that a user has been able to take an action, without the required permission.&lt;/p&gt;

&lt;p&gt;This could happen either accidentally or maliciously.&lt;/p&gt;

&lt;p&gt;In either case, unauthorized access is a serious threat to any organization.&lt;/p&gt;

&lt;p&gt;In the least severe cases, this could mean an internal user accidentally accesses non-sensitive data that they’re not supposed to have the permissions for. If this concerns an identifiable person, it’s still a breach, and should be reported and prevented from reoccurring.&lt;/p&gt;

&lt;p&gt;In more serious cases, unauthorized access can easily lead to DOS attacks, data leaks, or other cyber threats.&lt;/p&gt;

&lt;p&gt;In addition to the direct impact of these, you could face legal penalties, reputational damage, and other indirect costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preventing unauthorized access
&lt;/h3&gt;

&lt;p&gt;Naturally then, preventing unauthorized access is a top priority. There are several strategies you can employ to achieve this.&lt;/p&gt;

&lt;p&gt;First, note that there are essentially two kinds of unauthorized access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users who should have no access at all viewing or querying your data.&lt;/li&gt;
&lt;li&gt;Authorized users accessing data with the wrong permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, each of these has multiple different permutations. Just like before, either can be unintentional or malicious.&lt;/p&gt;

&lt;p&gt;For example, an internal user might mistakenly gain access to a particular resource. This is a very different situation from a malicious actor deliberately hacking into the same system.&lt;/p&gt;

&lt;p&gt;However, in both cases, prevention is critical. Effective data access control is a key tool here.&lt;/p&gt;

&lt;p&gt;Here are three strategies you can employ to prevent unauthorized access.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Combine entity-centric methods and strong authentication
&lt;/h4&gt;

&lt;p&gt;Combining entity-centric access control with effective authentication ensures two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only intended entities can access your data.&lt;/li&gt;
&lt;li&gt;Authenticated entities can only carry out actions where they have the correct permissions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Strong authentication is essential for preventing unauthorized access, for users and other entities alike. That way, you can ensure any action can be associated with the entity that initiated it.&lt;/p&gt;

&lt;p&gt;This includes effective password and identity management, as well as tools like SSO and two-factor authentication.&lt;/p&gt;

&lt;p&gt;We can then combine this with the access control methods we outlined earlier to control permissions for all authenticated users. Remember, we can do this by defining discrete roles with associated permissions, or with rules based on users’ existing attributes.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Clearly define and audit permission authorization
&lt;/h4&gt;

&lt;p&gt;Whichever data access control method you choose, permission authorization is vital. Permission authorization is the practice of allowing different kinds of users to take specific actions, either through their role, or attribute-based conditions.&lt;/p&gt;

&lt;p&gt;One element of this is ensuring that your framework for granting permissions is thoroughly documented. That is, that you have a clear record of how and when different permissions are assigned to users.&lt;/p&gt;

&lt;p&gt;This solves a few key problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Providing a blueprint for ongoing permission assignment.&lt;/li&gt;
&lt;li&gt;Streamlining the process of auditing and testing implementation.&lt;/li&gt;
&lt;li&gt;Creating a clear record of changes to your access control framework.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first point helps to prevent unauthorized access by ensuring that entities are assigned the correct permissions in the first place.&lt;/p&gt;

&lt;p&gt;The second and third are vital for allowing you to identify instances where users have been able to carry out actions that you didn’t intend to permit them to. You can then correct any oversights or issues that led to this.&lt;/p&gt;

&lt;p&gt;Additionally, this helps you to preempt situations where unauthorized access might occur, and put measures in place to prevent them.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Configure and implement additional controls
&lt;/h4&gt;

&lt;p&gt;Once you have a robust entity-centric framework in place, you can begin to think about other measures that will complement this. For example, using additional data and context-centric access control methods, to fill in specific gaps in your policy.&lt;/p&gt;

&lt;p&gt;These come in two varieties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Measures aimed at providing additional tailored controls over specific datasets.&lt;/li&gt;
&lt;li&gt;Measures aimed at preventing specific threats.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Say we have a large dataset, based on our customers’ details, which we’ve already implemented an RBAC system across.&lt;/p&gt;

&lt;p&gt;There are several ways we could use additional controls to further safeguard customer data.&lt;/p&gt;

&lt;p&gt;For example, we might want to put additional measures in place to prevent large-scale theft of our data. As we saw earlier, one way to do this would be to create contextual control, so that bulk exports can only be carried out during normal business hours.&lt;/p&gt;

&lt;p&gt;Alternatively, we could limit this permission to users with a particular IP address, to restrict bulk exports to head office staff.&lt;/p&gt;

&lt;p&gt;Or, we could create a dedicated tool for users to take specific actions on our data. For example, we could add an extra layer of security by creating a framework where certain actions can only be taken using a dedicated, locally hosted application.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to create a data access control model
&lt;/h2&gt;

&lt;p&gt;As we said at the outset, building a successful data access control model is a tricky balancing act. Specifically, the challenge is balancing the need for efficient access to data, with the need to ensure security, compliance, and privacy safeguards.&lt;/p&gt;

&lt;p&gt;However, these goals also intersect to a large extent.&lt;/p&gt;

&lt;p&gt;For example, you might have a platform that meets all of your security requirements but is difficult and inefficient to use. This could cause employees to try and circumvent your intended controls, leading to new security issues.&lt;/p&gt;

&lt;p&gt;This becomes even more difficult in the context of modern IT environments, where assets and resources are typically distributed across a wide range of physical locations.&lt;/p&gt;

&lt;p&gt;The key is developing systems that intelligently provide the right permissions for the right users.&lt;/p&gt;

&lt;p&gt;Budibase offers an advanced suite of features to help you secure your company data.&lt;/p&gt;

&lt;p&gt;With a built-in RBAC system, custom conditionality rules, free SSO, external data support, optional self-hosting, and autogenerated CRUD screens, Budibase is the ideal solution for creating secure, data-driven applications, with minimal coding skills.&lt;/p&gt;

&lt;p&gt;Check out our &lt;a href="https://budibase.com/product"&gt;product page&lt;/a&gt; to find out more.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>lowcode</category>
    </item>
  </channel>
</rss>
