Implementing Server‑Side Processing in DataTables for Large Datasets
Learn how to enable DataTables server‑side processing to load only the needed rows, with a complete initialization example, server response format, and common pitfalls to avoid.
13 Jul 2026, 12:05 UTC

Useful answer
Enable DataTables server‑side processing to keep the browser lightweight when working with tables that contain thousands or millions of rows. The table will request only the data needed for the current page, filter, or sort operation from your backend.
How it works – a worked configuration
First, include DataTables CSS and JS (version 2.x or later). Then initialise the table with serverSide: true and point the ajax option to your endpoint.
$(document).ready(function() {
$('#example').DataTable({
serverSide: true,
ajax: {
url: '/api/employees', // endpoint that returns JSON in the DataTables format
type: 'GET'
},
columns: [
{ data: 'id' },
{ data: 'first_name' },
{ data: 'last_name' },
{ data: 'position' },
{ data: 'office' },
{ data: 'salary' }
]
});
});
The client sends a standardized request on each interaction (page change, sort, search). A typical request looks like:
{
"draw": 1,
"start": 0,
"length": 10,
"search": { "value": "", "regex": false },
"order": [{ "column": 0, "dir": "asc" }]
}
Your server must parse these parameters, query the database accordingly, and return a JSON object with four required fields:
draw– echo the same value received.recordsTotal– total rows in the table (ignoring filtering).recordsFiltered– total rows after applying the search filter.data– array of row objects for the current page.
Example response (Node/Express pseudo‑code):
app.get('/api/employees', (req, res) => {
const draw = parseInt(req.query.draw) || 1;
const start = parseInt(req.query.start) || 0;
const length = parseInt(req.query.length) || 10;
const searchVal = req.query.search?.value ?? '';
// Build WHERE clause for search (simplified)
let where = '';
if (searchVal) {
where = `WHERE first_name LIKE '%${searchVal}%' OR last_name LIKE '%${searchVal}%'`;
}
// Total records (no filter)
const total = db.queryOne('SELECT COUNT(*) AS cnt FROM employees');
// Filtered records
const filtered = db.queryOne(`SELECT COUNT(*) AS cnt FROM employees ${where}`);
// Data page
const rows = db.queryAll(`SELECT id, first_name, last_name, position, office, salary FROM employees ${where} LIMIT ${start}, ${length}`);
res.json({
draw,
recordsTotal: total.cnt,
recordsFiltered: filtered.cnt,
data: rows
});
});
Limits and common mistakes
Incorrect recordsFiltered
If you return the same number for recordsTotal and recordsFiltered when a filter is active, the pagination controls will show the wrong page count. Always compute the filtered count separately.
Missing or mismatched draw
The draw value must be echoed exactly as received. Returning a different number causes DataTables to discard the response as stale, leading to a blank table or repeated requests.
Unvalidated length parameter
A malicious or buggy client could request an enormous length (e.g., 1 000 000), attempting to exhaust server resources. Apply a sane maximum (e.g., 100) and reject larger values with an error response.
Verification steps
- Open Browser DevTools → Network tab, filter for XHR.
- Change page, sort, or type in the search box and confirm a request to
/api/employeesappears. - Inspect the request payload to ensure
start,length, andsearch[value]are present. - Check the response JSON for the four required keys and that
drawmatches the request. - Verify that the table updates only with the returned
datarows and that the pagination reflectsrecordsTotalandrecordsFiltered.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.