Building a Useful Analytics Dashboard

Many dashboards developers build turn out to be useless: full of big numbers that look impressive but do not help anyone decide anything. These are vanity metrics. A good analytics...

Building a Useful Analytics Dashboard

Many dashboards developers build turn out to be useless: full of big numbers that look impressive but do not help anyone decide anything. These are vanity metrics. A good analytics dashboard answers real business questions: "are sales up this month?", "which products sell?", "when are the busy hours?". This tutorial shows how to build a genuinely useful dashboard in Laravel 11 — from choosing the right metrics and aggregating data with Eloquent, to feeding Chart.js and caching heavy queries.

1. Choose KPIs, Not Vanity Metrics

Before writing code, decide which metrics are actionable. Tell them apart:

  • Vanity metric: all-time total visitors. The number is huge but guides no action.
  • Useful KPI: revenue in the last 30 days versus the prior period, daily order count, top-selling products, average order value (AOV).

Rule of thumb: a metric earns its place if a decision changes when its value changes.

2. Aggregating Summary Cards

Summary cards use count() and sum() with a date-range filter. Avoid loading every row and computing in PHP — let the database do the math because it is far faster.

use App\Models\Order;
use Carbon\Carbon;

$start = Carbon::now()->subDays(30);

$revenue      = Order::where('created_at', '>=', $start)->sum('total');
$orderCount   = Order::where('created_at', '>=', $start)->count();
$avgOrderValue = $orderCount > 0 ? $revenue / $orderCount : 0;

To compare against the previous period (so you can show an up/down indicator), compute an equally long range just before this one:

$prevRevenue = Order::whereBetween('created_at', [
    Carbon::now()->subDays(60),
    Carbon::now()->subDays(30),
])->sum('total');

$growth = $prevRevenue > 0
    ? round(($revenue - $prevRevenue) / $prevRevenue * 100, 1)
    : 0; // growth percentage

3. Time-Series Aggregation for Charts

A trend chart needs data grouped per day. Use selectRaw with DATE() then groupBy. Return the result as date → total pairs.

$daily = Order::query()
    ->where('created_at', '>=', Carbon::now()->subDays(30))
    ->selectRaw('DATE(created_at) as day, SUM(total) as revenue')
    ->groupBy('day')
    ->orderBy('day')
    ->pluck('revenue', 'day');

Common problem: days with no transactions do not appear, so the chart has gaps. Fill empty dates with zero so the time axis stays clean:

$labels = [];
$values = [];
for ($i = 29; $i >= 0; $i--) {
    $date = Carbon::now()->subDays($i)->toDateString();
    $labels[] = $date;
    $values[] = (int) ($daily[$date] ?? 0);
}

4. A Controller That Returns Ready-to-Use Data

Gather all aggregates in the controller and pass them to the view. Add top products too using groupBy on order items:

public function index()
{
    $topProducts = OrderItem::query()
        ->selectRaw('product_name, SUM(qty) as sold')
        ->groupBy('product_name')
        ->orderByDesc('sold')
        ->limit(5)
        ->get();

    return view('dashboard', [
        'revenue'     => $this->revenue(),
        'growth'      => $this->growth(),
        'chartLabels' => $this->chartLabels(),
        'chartValues' => $this->chartValues(),
        'topProducts' => $topProducts,
    ]);
}

5. Feeding Chart.js with JSON

Chart.js only needs two arrays: labels and data. Send them from Blade as JSON with @json to stay safe from quoting issues:

<canvas id="revenueChart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
new Chart(document.getElementById('revenueChart'), {
    type: 'line',
    data: {
        labels: @json($chartLabels),
        datasets: [{
            label: 'Daily Revenue',
            data: @json($chartValues),
            borderColor: '#3b4b8b',
            tension: 0.3,
        }]
    }
});
</script>

6. Caching Heavy Aggregates

Aggregation queries can be heavy when data reaches millions of rows and the dashboard is opened often. Since analytics numbers do not have to be real-time, cache the result for a few minutes. This cuts database load dramatically.

use Illuminate\Support\Facades\Cache;

$daily = Cache::remember('dashboard.daily_revenue', now()->addMinutes(10), function () {
    return Order::query()
        ->where('created_at', '>=', Carbon::now()->subDays(30))
        ->selectRaw('DATE(created_at) as day, SUM(total) as revenue')
        ->groupBy('day')
        ->pluck('revenue', 'day');
});

If the data must be fresher after a new transaction, clear the cache in the order-created event: Cache::forget('dashboard.daily_revenue').

Common Mistakes to Avoid

  • Aggregating in PHP: pulling thousands of rows then calling collection sum() is very slow. Use the query builder's sum()/groupBy.
  • Showing vanity metrics: a big number without a comparison context does not help decisions. Always pair it with the previous period.
  • Forgetting to fill empty dates: the chart becomes misleading because zero days are skipped.

A useful dashboard is not about how many charts it has, but how quickly its users can decide. Focus on a few actionable KPIs, aggregate in the database, feed charts with clean JSON, and cache heavy queries — that is the recipe for a dashboard people actually use.

Yudhi
Written by
Yudhi
Founder & Lead Developer, GudangCode

Yudhi is the founder of GudangCode and a Laravel developer who has built dozens of ready-to-use business information systems — from POS and HRIS to management apps. He writes guides and articles on GudangCode to help Indonesian developers run, understand, and deploy Laravel source code correctly.

LaravelPHPMySQLSistem Informasi Bisnis See all articles by Yudhi
Want the full source code & apps?

Sign up free to download ready-to-use business applications, information systems, and Laravel source code.

Sign Up Free & Download
Dashboard Analitik Sistem Informasi
Share this article
Back to Blog
📚 Free Learning Hub

Learn Coding for Free at DhieCoderWeb

Explore Laravel, PHP, JavaScript tutorials, source code, web development guides, and practical programming tips.

DhieCoderWeb
100+
Tutorials
Free
Learning
SEO
Tips
Visit Dhiecoderweb.com →

Get Full Access Now!

Join our membership and unlock exclusive access to all premium features. Fast, easy, and ready to use instantly.

Join Membership Now
Tim Support
Online
Isi data dulu untuk mulai chat:
Beri rating & testimoni sebelum menutup:
Live chat by gudangcode.com