Building Modern Web Apps: AdonisJS, SSR, and Cloud Run
Developing robust, scalable, and delightful web applications requires a thoughtful integration of backend frameworks, deployment platforms, and frontend rendering strategies. This post demonstrates how to combine the elegant AdonisJS framework, Server-Side Rendering (SSR), and Google Cloud Run for a superior Developer Experience (DX) and compelling UI/UX, all powered by TypeScript.
AdonisJS: The Full-Stack TypeScript Powerhouse
AdonisJS v6 offers a comprehensive, batteries-included experience, excelling in rapid development with TypeScript. Its conventions, powerful ORM (Lucid), validation, and testing utilities significantly boost developer productivity.
Let's set up a basic AdonisJS controller that fetches data and renders an SSR view.
// app/controllers/posts_controller.ts
import Post from '#models/post'
import { HttpContext } from '@adonisjs/core/http'
export default class PostsController {
async index({ view }: HttpContext) {
// Assuming a Post model exists and fetches data from a database
const posts = await Post.all()
return view.render('pages/posts/index', { posts })
}
async show({ view, params }: HttpContext) {
const post = await Post.findByOrFail('slug', params.slug)
return view.render('pages/posts/show', { post })
}
}
The corresponding route:
// start/routes.ts
import router from '@adonisjs/core/services/router'
router.get('/posts', [PostsController, 'index'])
router.get('/posts/:slug', [PostsController, 'show'])
AdonisJS's commitment to TypeScript throughout its ecosystem ensures type safety and excellent IDE support, drastically improving DX. Refer to the AdonisJS documentation for setup details.
Server-Side Rendering (SSR) with Edge
AdonisJS leverages its powerful Edge templating engine for SSR. This provides benefits like improved SEO, faster initial page loads, and a simpler development model compared to client-side rendering (CSR) for content-heavy applications.
Here's a snippet of an Edge template (resources/views/pages/posts/index.edge):
@! extends('layouts/app')
@section('content')
<div class="container mx-auto px-4 py-8">
<h1 class="text-4xl font-bold mb-6">Our Latest Posts</h1>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@each(post in posts)
<article class="bg-white p-6 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-300">
<h2 class="text-2xl font-semibold mb-2">
<a href="/posts/{{ post.slug }}" class="text-blue-600 hover:underline">
{{ post.title }}
</a>
</h2>
<p class="text-gray-700 text-sm">Published on {{ post.createdAt.toFormat('MMM dd, yyyy') }}</p>
<p class="mt-4 text-gray-800">{{ post.excerpt }}</p>
</article>
@endeach
</div>
</div>
@endsection
This template uses a base layout (layouts/app.edge) and iterates over the posts array passed from the controller. Edge's syntax is clean and intuitive, making it easy to create maintainable and readable frontend code directly within your AdonisJS application.
Elevating UI/UX Design
While Edge handles rendering, the actual UI/UX quality depends on your design system. Integrating a utility-first CSS framework like Tailwind CSS allows for rapid development of beautiful, responsive interfaces. For this example, assume Tailwind CSS classes are applied directly in your Edge templates or compiled from your postcss.config.js.
Focus on key principles:
- Responsiveness: Utilize media queries (Tailwind's breakpoints) to ensure optimal display on all devices.
- Accessibility: Employ semantic HTML, ARIA attributes where necessary, and clear focus states.
- Clarity: Prioritize intuitive navigation, consistent styling, and clear calls to action to enhance user interaction.
Deploying to Google Cloud Run: Serverless Scalability
Google Cloud Run offers a fully managed, serverless platform for containerized applications. It automatically scales up and down based on traffic, from zero to thousands of instances, making it cost-effective and highly resilient.
First, package your AdonisJS application into a Docker image.
# Dockerfile
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Install pnpm
RUN npm install -g pnpm
# Copy package.json and pnpm-lock.yaml to install dependencies
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --prod --frozen-lockfile
# Copy the rest of the application code
COPY . .
# Build AdonisJS application
RUN pnpm build
# Expose the port AdonisJS listens on (default is 3333)
EXPOSE 3333
# Start the application
CMD ["node", "build/server.js"]
To deploy, build your image and push it to Google Container Registry (GCR) or Artifact Registry, then deploy with Cloud Run:
gcloud builds submit --tag gcr.io/YOUR_PROJECT_ID/adonis-app
gcloud run deploy adonis-app --image gcr.io/YOUR_PROJECT_ID/adonis-app \
--platform managed --region us-central1 --allow-unauthenticated \
--set-env-vars NODE_ENV=production,PORT=8080 # Cloud Run expects HTTP on PORT 8080
Cloud Run abstracts away server management, allowing you to focus purely on your application logic. Learn more at the Google Cloud Run documentation.
Maximizing Developer Experience (DX) and Documentation
Beyond AdonisJS's inherent DX, cultivate an excellent development environment:
- Clear Project Structure: AdonisJS provides a logical structure. Adhere to it consistently.
- Automated Testing: Leverage AdonisJS's testing suite (
@adonisjs/core/test_utils) for robust unit and integration tests. - Environment Management: Use
.envfiles for configuration, but never commit them to version control. - Internal Documentation: Maintain a comprehensive
README.md, contribute clear code comments (JSDoc/TSDoc), and consider tools like@adonisjs/swaggerfor API documentation (if building a REST API). - CI/CD: Automate builds and deployments to Cloud Run using GitHub Actions or Cloud Build to streamline your workflow.
Conclusion
Integrating AdonisJS with SSR on Google Cloud Run creates a powerful, scalable, and developer-friendly application stack. By focusing on AdonisJS's robust features, Edge for efficient SSR, thoughtful UI/UX design, and Cloud Run's serverless capabilities, you can deliver high-quality web applications with exceptional developer experience and optimal performance. This integrated approach simplifies deployment, enhances maintainability, and ultimately leads to more successful projects.