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!
Prerequisites
Before we start, make sure you have these things installed on your computer
- Node.js (v18 or later)
- Npm (v10 or later)
What and Why Encore
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:
- The local environment matches the cloud
- Cross-service type-safety
- Type-aware infrastructure
- Automatic API docs & clients
- Local test tracing
- And more
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.
Installing Encore
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 Encore application
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.
What are Streaming APIs in Encore
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:
- StreamIn: Use this to stream data into your service.
- StreamOut: Use this to stream data out from your service.
- StreamInOut: Use this to stream data both into and out of your service.
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.
Creating dashboard service
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:
- id: auto-incremented and serves as the primary key
- sale: title of the sale
- total: total amount of the sale
- date: date of the sale
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:
- StreamOptions:
- expose: Set to true to make the endpoint public, otherwise false
- auth: Set to true to protect the endpoint with authentication, otherwise false
- path: /sale
- Function: It takes two arguments
- handshake: Used to establish the stream connection
- stream: The stream object
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.
Exploring the development dashboard
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:
- Service Catalog and API Explorer for easily making API calls to your local backend
- Distributed Tracing for easy and powerful debugging
- Automatic API Documentation for sharing knowledge and answering questions
- Encore Flow for visualizing your microservices architecture
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.
Generating the client
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.
Creating frontend service
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:
- Serve the dashboard and frontend services separately
- Serve everything as a single bundle (we will use this approach for this tutorial)
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.
- path: /!path ensures it acts as a fallback route and doesn't conflict with other endpoints.
- dir: ./dist is the directory of the build version of the React application.
Great, the static endpoint is set up. Now, let's install a few dependencies for our React application
- react-router-dom
- uuid
- tailwind css
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.
- SalesTable: to display the sales data in a table format.
# 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.
- SalesMetrics: to show some sales numbers like total, lowest, and average sale.
# 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);
- RoleSelector: To let users choose a role for the dashboard, we will show two options:
- Viewer: Can view the sales dashboard
- Manager: Can view and create new sales
# 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[]; }
- GenerateSales: to display the generate sale button and contain the logic for generating sales.
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 application
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.
- Viewer
- Manager
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.
Summary
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:
- Type Safety: Makes sure all API endpoints and data structures are safe, reducing mistakes and making code more reliable.
- Streaming APIs: Allows real-time data streaming with StreamIn, StreamOut, and StreamInOut, enabling two-way communication between the client and server.
- Local Development Dashboard: Offers tools for testing and debugging, like a Service Catalog, API Explorer, and Distributed Tracing, boosting productivity.
- Automatic Client Generation: Creates frontend request clients in TypeScript or JavaScript, keeping request/response types aligned.
- Simplified Microservices: Lets you build apps with multiple services without the usual complexity, providing an easier way to handle microservices.
These features together make it easier to build and manage complex apps, offering a great developer experience.
Relevant links
- Star Encore on GitHub
- Find the Source Code for this example
- Join our Discord Community
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!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics











JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

Different JavaScript engines have different effects when parsing and executing JavaScript code, because the implementation principles and optimization strategies of each engine differ. 1. Lexical analysis: convert source code into lexical unit. 2. Grammar analysis: Generate an abstract syntax tree. 3. Optimization and compilation: Generate machine code through the JIT compiler. 4. Execute: Run the machine code. V8 engine optimizes through instant compilation and hidden class, SpiderMonkey uses a type inference system, resulting in different performance performance on the same code.

JavaScript is the core language of modern web development and is widely used for its diversity and flexibility. 1) Front-end development: build dynamic web pages and single-page applications through DOM operations and modern frameworks (such as React, Vue.js, Angular). 2) Server-side development: Node.js uses a non-blocking I/O model to handle high concurrency and real-time applications. 3) Mobile and desktop application development: cross-platform development is realized through ReactNative and Electron to improve development efficiency.

This article demonstrates frontend integration with a backend secured by Permit, building a functional EdTech SaaS application using Next.js. The frontend fetches user permissions to control UI visibility and ensures API requests adhere to role-base

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

The shift from C/C to JavaScript requires adapting to dynamic typing, garbage collection and asynchronous programming. 1) C/C is a statically typed language that requires manual memory management, while JavaScript is dynamically typed and garbage collection is automatically processed. 2) C/C needs to be compiled into machine code, while JavaScript is an interpreted language. 3) JavaScript introduces concepts such as closures, prototype chains and Promise, which enhances flexibility and asynchronous programming capabilities.

I built a functional multi-tenant SaaS application (an EdTech app) with your everyday tech tool and you can do the same. First, what’s a multi-tenant SaaS application? Multi-tenant SaaS applications let you serve multiple customers from a sing
