Skip to content

Project Detail View

On project-detail.ejs, we are looking at the specific project. Now we naturally want to iterate over all the projectImages and render the gallery layout.

{{/* views/project-detail.ejs */}}
<article>
<h2><%= project.title %></h2>
<% // Find the featured image (hero banner) const featuredImage =
project.projectImages?.find(img => img.isFeatured); // Filter out the featured
image to get the remaining gallery images const galleryImages =
project.projectImages?.filter(img => !img.isFeatured); %> <% if
(featuredImage) { %>
<img
src="/uploads/<%= project.slug %>/<%= featuredImage.filename %>"
alt="<%= featuredImage.altText || project.title %>"
style="width: 100%; max-height: 400px; object-fit: cover; border-radius: 8px; margin-bottom: 1.5rem;"
/>
<% if (featuredImage.caption) { %>
<p
style="text-align: center; font-style: italic; color: #666; margin-top: -1rem;"
>
<%= featuredImage.caption %>
</p>
<% } %> <% } %>
<p><%= project.description %></p>
<% if (project.categoryId) { %>
<p>
<strong>Category:</strong> <%= project.categoryId.name ?
project.categoryId.name : project.categoryId %>
</p>
<% } %> <% if (project.tags && project.tags.length) { %>
<p><strong>Tags:</strong> <%= project.tags.map(t => t.name).join(", ") %></p>
<% } %> <% if (galleryImages && galleryImages.length > 0) { %>
<hr style="margin: 2rem 0;" />
<h3>Project Gallery</h3>
<div
class="gallery"
style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem;"
>
<% galleryImages.forEach(img => { %>
<figure style="margin: 0;">
<img
src="/uploads/<%= project.slug %>/<%= img.filename %>"
alt="<%= img.altText || project.title %>"
style="width: 100%; height: 200px; object-fit: cover; border-radius: 4px;"
/>
<% if (img.caption) { %>
<figcaption style="font-size: 0.85rem; color: #555; margin-top: 0.5rem;">
<%= img.caption %>
</figcaption>
<% } %>
</figure>
<% }) %>
</div>
<% } %>
<a href="/projects"> ← Back to Projects</a>
</article>

We’ve successfully built a relational database mapper and navigated the complexities of processing binary document streams, splitting out metadata from the binary data, and rendering it all in the front end! Let’s take a look back at what we’ve accomplished in this section.