Labs ICT
Pro Login

Data Binding

Two-way data flow in EJS templates.

One-Way Data Flow

In EJS, data flows in one direction — from your Express route to the template. The template receives data and displays it, but cannot modify the original data source.


app.get('/dashboard', (req, res) => {
  const user = { name: 'Bilal', score: 100 };
  res.render('dashboard', { user });
});
    

The template reads the data but the original user object stays unchanged.


<h1>Welcome, <%= user.name %></h1>
<p>Your score: <%= user.score %></p>
    
Try it Yourself →

Passing Props to Includes

When you include a partial, you can pass data to it. This makes partials truly reusable because they adapt to the data they receive.


<%- include('partials/avatar', { 
  src: user.avatar, 
  alt: user.name, 
  size: 'large' 
}) %>
    

The avatar partial uses these values without knowing where they come from.


<!-- partials/avatar.ejs -->
<img src="<%= src %>" alt="<%= alt %>" 
     class="avatar avatar-<%= size %>">
    

Data Nesting

You can pass nested objects and arrays to templates. Access deeply nested values using dot notation.


app.get('/order', (req, res) => {
  res.render('order', {
    order: {
      id: 12345,
      customer: {
        name: 'Sara',
        email: 'sara@example.com'
      },
      items: [
        { name: 'Laptop', price: 999 },
        { name: 'Mouse', price: 25 }
      ]
    }
  });
});
    

Access nested properties and iterate through arrays.


<h2>Order #<%= order.id %></h2>
<p>Customer: <%= order.customer.name %></p>

<ul>
  <% order.items.forEach(item => { %>
    <li><%= item.name %> - $<%= item.price %></li>
  <% }); %>
</ul>
    
Try it Yourself →

Destructuring in Templates

You can destructure objects directly in your EJS code to keep things clean and readable.


<% const { name, email, role } = user; %>

<div class="profile">
  <h3><%= name %></h3>
  <p><%= email %></p>
  <span class="badge"><%= role %></span>
</div>
    

This is especially useful when you only need a few properties from a large object.


<% const { title, excerpt, author, publishedAt } = post; %>

<article>
  <h2><%= title %></h2>
  <p class="meta">By <%= author %> on <%= publishedAt %></p>
  <p><%= excerpt %></p>
</article>
    

Combining Data Sources

You can combine data from multiple sources — route parameters, query strings, and your database — into a single data object for the template.


app.get('/posts/:category', async (req, res) => {
  const posts = await Post.find({ category: req.params.category });
  const category = await Category.findOne({ slug: req.params.category });
  
  res.render('posts', {
    posts,
    category,
    page: parseInt(req.query.page) || 1
  });
});
    

The template has everything it needs to render a complete page.


<h1><%= category.name %> Posts</h1>
<p>Page <%= page %></p>

<% posts.forEach(post => { %>
  <article>
    <h3><%= post.title %></h3>
    <p><%= post.summary %></p>
  </article>
<% }); %>