Package · mui-data-grid

MUI Data Grid

Winter MUI Data Grid is a server-side adapter for the MUI X DataGrid table. You declare a column schema; the library takes the grid’s request (page, sort model, filter model), validates it against that schema as a whitelist, layers the conditions onto your repository and returns { rowCount, rows } — exactly what the grid expects from a server.

The problem

MUI X DataGrid can run in server mode: instead of holding the whole table in the browser, it sends its state to the server — which page to show, which columns to sort by, which filters the user applied. It looks like this:

json
{
"page": 0,
"pageSize": 25,
"sortModel": [{ "field": "views", "sort": "desc" }],
"filterModel": {
  "logicOperator": "and",
  "items": [{ "field": "title", "operator": "contains", "value": "winter" }]
}
}

What follows is manual work rewritten in every project, badly in the same way each time: parse that JSON, map the browser’s field to a database column, translate operator into SQL, remember LIMIT/OFFSET, count the total with a separate COUNT. And above all — don’t let the client sort by a column they shouldn’t see, or slip something into a filter that reaches the query verbatim.

The naive implementation (“take field and drop it into ORDER BY”) opens SQL injection through a column name. The careful one turns into a hundred lines of branching per endpoint.

The solution

The library does that work once and asks you for a single declaration — the schema:

php
$schema = GridSchema::make(
  GridColumn::for('title',      'a.title')->filterable(FilterType::String)->sortable(),
  GridColumn::for('views',      'a.views')->filterable(FilterType::Number)->sortable(),
  GridColumn::for('authorName', 'au.name')->filterable(FilterType::String)->sortable(),
)->defaultOrder('a.created_at DESC');

return MuiGrid::wrap($repo, $request, $schema);

The schema is the contract between the table in the browser and the database. It states which field names exist, which SQL expression each one maps to, what may be done with it (filter, sort) and what type its values are. Anything not in the schema never reaches the database.

Philosophy

  • An overlay, not a generator. The library does not build your query. SELECT, JOIN and the business WHERE are yours; it only appends andWhere() for filters, sets orderBy() and hands pagination to the framework’s Paginator. That is why anything expressible in SQL — CTEs, subqueries, computed columns — just works.
  • The schema is a whitelist. Not “filter everything except what’s forbidden” but “filter only what’s declared”. You cannot forget to lock a column down: a column absent from the schema is closed by default.
  • Names come from your code, values from the request. A column’s SQL expression comes from the schema and goes into the query template as written; a user’s value always travels as a bound parameter. Injection is impossible by construction.
  • The dialect belongs to the connection, not the grid. Case-insensitive search is spelled differently in PostgreSQL, MySQL and SQLite. The library resolves the spelling from the repository’s driver, so one schema works on any of the three.

Core concepts

  • GridSchema — a set of columns plus a fallback ORDER BY. The single source of truth for what the client is allowed to do.
  • GridColumn — one column: the browser’s field name → an SQL expression, plus the “may be filtered” and “may be sorted” flags.
  • FilterType — a column’s value type (String, Number, Boolean, Date). Decides which operators the column accepts: a numeric column refuses contains, a string column refuses >.
  • MuiGridRequest — the parsed grid request. Hydrated by Winter’s request layer; there is nothing to parse by hand.
  • MuiGrid::wrap() — the entry point: takes a repository, a request and a schema, returns a MuiGridResponse.
  • TextMatch — how case-insensitive matching is spelled (ILIKE / LIKE / lower()). Resolved automatically by default.

Features

  • Pagination with a total countLIMIT/OFFSET and COUNT in one call, through the framework’s Paginator.
  • Filters gated by type — all twenty-three MUI operators, each allowed only for a suitable FilterType.
  • AND / OR — as given by filterModel.logicOperator.
  • Multi-column sorting — from the sort model, with a fallback order when there is none.
  • Custom sort expressions — a column can filter on a.title and sort on lower(a.title).
  • Per-column filter overridesfilterUsing() for cases that need their own SQL.
  • Row mapper — turn flat rows into resources without leaving the call.
  • Portability — PostgreSQL, MySQL and SQLite with no code changes.

Good fit for

Any admin screen or listing with more data than is sensible to ship to a browser: the table stays responsive because one page travels to the client, not the whole set.

Requirements

Composer installs both for you. Details in Installation.

Installation

bash
composer require flytachi/winter-mui-data-grid

What it looks like

With the schema declared and the request in hand, all that’s left is the response:

php
$repo = ArticleRepository::instance('a')
  ->select('a.id, a.title, a.views')
  ->where(Qb::eq('a.is_published', true));

return ResponseEntity::ok(
  MuiGrid::wrap($repo, $request, $schema)->toArray()
);

The response:

json
{ "rowCount": 134, "rows": [ { "id": 12, "title": "Winter internals", "views": 9001 } ] }

The full path — from installing the package to a working table in the browser — is in the Quickstart.

Continue with Installation, Quickstart and the Mental model.