Home >Web Front-end >JS Tutorial >How to Build a Real-Time Dashboard with Encore.ts and React
Real-time dashboards are incredibly useful in various applications, from tracking website analytics to monitoring live financial data or even keeping tabs on IoT devices.
? In this tutorial, we’ll show you how to build one using React and Encore.ts. You’ll learn to create a dynamic dashboard that streams updates instantly, empowering you to make quick, informed decisions.
To get a glimpse of what we’ll build, check out this GIF of the finished product and the source code here. Let’s dive in!
Before we start, make sure you have these things installed on your computer
Encore.ts is an open-source framework that helps you build backends with TypeScript, ensuring type safety. It's lightweight and fast because it doesn't have any NPM dependencies.
When developing distributed backend systems, it's often hard to replicate the production environment locally, leading to a poor developer experience. You may end up dealing with a lot of complexity just to get things running locally in a reasonable way, which takes time from focusing on building the actual application. Encore.ts addresses this by providing a complete toolset for building distributed systems, including:
Alright, we talked about what Encore is and how it helps us build backend services. In the next section, let's install Encore locally and start building.
To work with Encore, we need to install the CLI, which makes it very easy to create and manage local environments.
# macOS brew install encoredev/tap/encore # Windows iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash
Creating an Encore application is very easy, you just need to run the command.
encore app create
You will be asked the following questions, so choose your answers accordingly.
Select language for your applicatio : TypeScript Template: Empty app App Name : real-time-dashboard
Once the App is created then you can verify the application config in encore.app
{ "id": "real-time-dashboard-<random-id>", "lang": "typescript" }
All right, we have created the Encore application. Let's talk about Streaming APIs in Encore in the next section.
Before we talk about streaming APIs, let's discuss APIs in Encore. Creating an API endpoint in Encore is very easy because it provides the api function from the module encore.dev/api to define the API endpoint with type safety. Encore also has built-in validation for incoming requests. At their core, APIs are simple async functions with a request and response schema structure. Encore parses the code and generates the boilerplate at compile time, so you only need to focus on defining the APIs.
Streaming APIs are APIs that let you send and receive data to and from your application, allowing two-way communication.
Encore offers three types of streams, each for a different data flow direction:
When you connect to a streaming API endpoint, the client and server perform a handshake using an HTTP request. If the server accepts this request, a stream is created for both the client and the API handler. This stream is actually a WebSocket that allows sending and receiving messages.
Alright, now that we know what APIs and Streaming APIs are in Encore, let's create our dashboard service in the next section with Streaming API endpoints to store and retrieve data in real time.
Let's create a dashboard service where we'll define our sales API to stream data to and from our sales dashboard.
Create a folder at the root level called dashboard and then add an encore.service.ts file. This file will tell Encore to treat the dashboard folder and its subfolders as part of the service.
# macOS brew install encoredev/tap/encore # Windows iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash
Then add the following code to the encore.service.ts file. We import the Service class from encore.dev/service and create an instance of it by using "dashboard" as the service name.
encore app create
Now let's create a dashboard.ts file and set up the sale API.
Select language for your applicatio : TypeScript Template: Empty app App Name : real-time-dashboard
Before setting up the API, we will first set up the database to store the sales data. We will use SQLDatabase from the module encore.dev/storage/sqldb to create a PostgreSQL database supported by Encore.
We need to define SQL as a migration, which Encore will pick up when we execute the command encore run.
Create a folder named migrations inside the dashboard folder, and then create a file called 1_first_migration.up.sql. Make sure to follow the naming convention, starting with number_ and ending with up.sql.
# macOS brew install encoredev/tap/encore # Windows iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash
Here, we are creating a table called sales with four columns:
Next, add the following code to the dashboard.ts file.
encore app create
Here, we create an instance of SQLDatabase by giving it the name dashboard and specifying the path to the migrations folder.
We are using the postgres package to listen for and notify changes in the database.
?
Next, add these types and an in-memory map to hold the connected streams (websocket connections).
Select language for your applicatio : TypeScript Template: Empty app App Name : real-time-dashboard
Next, let's set up a sale streaming endpoint to send updates when a new sale happens.
{ "id": "real-time-dashboard-<random-id>", "lang": "typescript" }
Here we use the api.streamOut function to define the API, which takes two arguments:
We keep connections in the connectedStreams map and listen to the new_sale channel using a Postgres client. When a new sale happens, we send updates to all connected streams.
Next, we will define the add sale API endpoint, where we get the sale data from the request body and use the db instance to insert the new sale record.
# create dashboard folder mkdir dashboard # switch to dashboard folder cd dashboard # create encore.service.ts file inside dashboard folder touch encore.service.ts
Here, after adding the new sale record to the database, we use the Postgres client to send a notification to the new_sale channel with the sale data. This way, the new_sale channel listener gets notified and can take action.
Lastly, let's set up the API endpoint to return the list of sales records.
import { Service } from 'encore.dev/service'; export default new Service('dashboard');
Here, we use the db instance method query to get the data and then process it to return as a list.
Great, we now have all the API endpoints defined. Let's explore the Encore development dashboard in the next section.
We have API endpoints with a database setup, but how do we test and debug the services? Don't worry, because Encore provides a Local Development dashboard to make developers' lives easier and boost productivity.
It includes several features to help you design, develop, and debug your application:
All these features update in real time as you change your application.
To access the dashboard, start your Encore application with encore run, and it opens automatically.
# macOS brew install encoredev/tap/encore # Windows iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash
This is how the dashboard looks, and you can test everything locally before going to production. This makes it much easier to test microservices architecture without needing external tools.
Here is an example of adding a new sale using the API explorer. When you click the Call API button, you will get a response and a log. On the right side, you can see the trace of the request.
When you click on the trace link, you get details like database queries, responses, and logs.
Alright, that's all about the local development dashboard. You can explore other options like the Service catalog, flow, and more. In the next section, we'll generate the client with TypeScript type safety to use in the Frontend service (React Application) to communicate with dashboard service APIs.
Encore can generate frontend request clients in TypeScript or JavaScript, keeping request/response types in sync and helping you call the APIs without manual effort.
Create a folder named frontend in the root directory and run the following command to set up the React project using Vite.
encore app create
Next, create a lib folder inside the src directory, add a new file called client.ts, and leave it empty.
Select language for your applicatio : TypeScript Template: Empty app App Name : real-time-dashboard
Then, in the package.json file, add a new script called gen-client.
{ "id": "real-time-dashboard-<random-id>", "lang": "typescript" }
Next, run the script to create the client in src/lib/client.ts.
# create dashboard folder mkdir dashboard # switch to dashboard folder cd dashboard # create encore.service.ts file inside dashboard folder touch encore.service.ts
The src/lib/client.ts file should contain the generated code.
import { Service } from 'encore.dev/service'; export default new Service('dashboard');
Next, create a file named getRequestClient.ts in the lib directory and add the following code. This will return the Client instance based on the environment.
# make sure you are in dashboard folder touch dashboard.ts
Alright, now we have the client to use in a React application to call the dashboard APIs. In the next section, let's create the frontend service and build the UI for the real-time sales dashboard.
In the previous section, we set up a frontend folder with a React application, and now we want to make it a service. Let's create an encore.service.ts file and add the following code to tell Encore to treat the frontend folder as a "frontend" service.
# macOS brew install encoredev/tap/encore # Windows iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash
We have two options:
To serve the React application, we need to build and serve it as static assets in Encore. Let's set up the static.ts file in the frontend folder.
Serving static files in Encore.ts is similar to regular API endpoints, but we use the api.static function instead.
encore app create
Here are two important things to note: we are passing the path and dir options.
Great, the static endpoint is set up. Now, let's install a few dependencies for our React application
Select language for your applicatio : TypeScript Template: Empty app App Name : real-time-dashboard
Then update the main.tsx with the code below.
{ "id": "real-time-dashboard-<random-id>", "lang": "typescript" }
Next, let's set up Tailwind CSS and update a few files.
# create dashboard folder mkdir dashboard # switch to dashboard folder cd dashboard # create encore.service.ts file inside dashboard folder touch encore.service.ts
Change the content section in tailwind.config.js
import { Service } from 'encore.dev/service'; export default new Service('dashboard');
And index.css with the following code.
# make sure you are in dashboard folder touch dashboard.ts
Now let's create a few components for the sales dashboard.
# 1_first_migration.up.sql CREATE TABLE sales ( id BIGSERIAL PRIMARY KEY, sale VARCHAR(255) NOT NULL, total INTEGER NOT NULL, date DATE NOT NULL );
Here, we are importing the types from the generated client to match the dashboard service type and ensure type safety.
# dashboard.ts import { SQLDatabase } from 'encore.dev/storage/sqldb'; import postgres from 'postgres'; const db = new SQLDatabase('dashboard', { migrations: './migrations', }); const client = postgres(db.connectionString);
# dashboard.ts ... // Map to hold all connected streams const connectedStreams: Map<string, StreamOut<Sale>> = new Map(); interface HandshakeRequest { id: string; } interface Sale { sale: string; total: number; date: string; } interface ListResponse { sales: Sale[]; }
To generate sales, we'll need some mock data, so let's create a src/constant.ts file and add the mock data
# dashboard.ts ... import { api, StreamOut } from 'encore.dev/api'; import log from 'encore.dev/log'; ... export const sale = api.streamOut<HandshakeRequest, Sale>( { expose: true, auth: false, path: '/sale' }, async (handshake, stream) => { connectedStreams.set(handshake.id, stream); try { await client.listen('new_sale', async function (data) { const payload: Sale = JSON.parse(data ?? ''); for (const [key, val] of connectedStreams) { try { // Send the users message to all connected clients. await val.send({ ...payload }); } catch (err) { // If there is an error sending the message, remove the client from the map. connectedStreams.delete(key); log.error('error sending', err); } } }); } catch (err) { // If there is an error reading from the stream, remove the client from the map. connectedStreams.delete(handshake.id); log.error('stream error', err); } } );
# dashboard.ts ... ... export const addSale = api( { expose: true, method: 'POST', path: '/sale/add' }, async (body: Sale & { id: string }): Promise<void> => { await db.exec` INSERT INTO sales (sale, total, date) VALUES (${body.sale}, ${body.total}, ${body.date})`; await client.notify( 'new_sale', JSON.stringify({ sale: body.sale, total: body.total, date: body.date }) ); } );
Here, we import the getRequestClient and then call the addSale endpoint from the dashboard service. It's very simple, and addSale is type-safe, so if you try to pass any attributes that aren't allowed, you'll get an error.
Next, let's create a SalesDashboard component to show the dashboard view with sales metrics, recent sales, and all-time sales.
# macOS brew install encoredev/tap/encore # Windows iwr https://encore.dev/install.ps1 | iex # Linux curl -L https://encore.dev/install.sh | bash
SalesDashboard takes one prop called role, which determines if it will show the GenerateSales component.
saleStream will hold the active stream reference and is strongly typed.
encore app create
When the component mounts, we create the stream connection using the sale endpoint of the dashboard service. We then listen for the socket open and close events and run the appropriate logic based on these events.
We read the sale data from saleStream.current and store it in the recentSalesData state.
When the component unmounts, we clean up and close the current stream.
Select language for your applicatio : TypeScript Template: Empty app App Name : real-time-dashboard
This code gets the stored sales using the listSales endpoint from the dashboard service and saves them in the salesData state.
{ "id": "real-time-dashboard-<random-id>", "lang": "typescript" }
This code calculates the recent sales and all-time sales data.
# create dashboard folder mkdir dashboard # switch to dashboard folder cd dashboard # create encore.service.ts file inside dashboard folder touch encore.service.ts
Finally, update the App.tsx file with this code.
import { Service } from 'encore.dev/service'; export default new Service('dashboard');
Here, we are showing the SalesDashboard and RoleSelector components based on whether the role query parameter is available or not.
Now, let's build the React application by running the command below in the frontend root.
# make sure you are in dashboard folder touch dashboard.ts
Once you run the command successfully, the dist folder will be created inside the frontend directory.
Great, now in the next section, let's run the application and test it from start to finish.
Running the encore application is easy; just use the command below.
# 1_first_migration.up.sql CREATE TABLE sales ( id BIGSERIAL PRIMARY KEY, sale VARCHAR(255) NOT NULL, total INTEGER NOT NULL, date DATE NOT NULL );
Once you run the command successfully, you will see logs in the terminal like this:
# dashboard.ts import { SQLDatabase } from 'encore.dev/storage/sqldb'; import postgres from 'postgres'; const db = new SQLDatabase('dashboard', { migrations: './migrations', }); const client = postgres(db.connectionString);
Visit http://127.0.0.1:4000 in your browser, and you will see a screen like the one below.
Next, choose Viewer in one tab and Manager in another tab.
While checking the development dashboard, we created a sale record, and it was saved in the database, so it's also visible in the UI.
Now, from the manager view, click on the Generate Sales button and watch as both tabs on the dashboard update in real-time.
In this tutorial, we created a real-time sales dashboard using React and Encore.ts. The app updates instantly with new sales and inventory items, helping with quick decisions. We used Encore.ts, an open-source framework, to build the backend with TypeScript for safe and smooth coding. Key features of Encore are:
These features together make it easier to build and manage complex apps, offering a great developer experience.
The above is the detailed content of How to Build a Real-Time Dashboard with Encore.ts and React. For more information, please follow other related articles on the PHP Chinese website!