See how to build an Angular application with Analog, enjoying a modern, smooth developer experience.
Angular has long been a powerful framework for building robust web applications. And now, with Analog, you can enjoy a more streamlined developer experience and modern full-stack capabilities comparable to frameworks like Next.js and Nuxt.
In this article, we’ll explore how to build full-stack applications with Analog, covering everything from file-based routing to server-side rendering. If you’re already familiar with TypeScript and Angular, you’ll find Analog to be a natural and powerful extension of what you already know.
Analog is a full-stack meta-framework built on top of Angular. It brings modern features like file-based routing, API routes, server-side rendering and more to the Angular ecosystem. Think of it as Angular’s answer to Next.js or Nuxt.js—a framework that extends Angular to make building full-stack applications more intuitive and developer-friendly. The server and deployment integrations are powered by Nitro.
Getting started with Analog is straightforward. Let’s create a new project using the create-analog package and your choice of package manager (this tutorial uses npm).
Open your terminal and run the command npm create analog@latest my-analog-app
. You’ll be asked how you’d like your application to be bootstrapped. Pick Full-stack Application
, and select No
for Analog SFC and Tailwind.
Let’s follow the CLI prompt to install and run the application we just created. The following commands will help us do that:
# Navigate to the project directory
cd my-analog-app
# Install dependencies
npm install
# Start the development server
npm run dev
This starts your app with a development server running at http://localhost:5173
.
One of Analog’s standout features is file-based routing, which simplifies the way you organize and navigate between pages in your application.
In Analog, every file in the src/app/pages
directory that ends with .page.ts
becomes a route in your application. The route path is determined by the file path:
src/app/pages/
├── index.page.ts -> /
├── about.page.ts -> /about
├── users/
│ ├── index.page.ts -> /users
│ └── [id].page.ts -> /users/:id (dynamic route)
└── blog/
└── [...slug].ts -> /blog/* (catch-all route)
It’s possible to define nested routes in two different ways:
src/app/pages/about/team.page.ts
defines an /about/team
route.src/app/pages/about.team.page.ts
also defines an /about/team
route.We currently have pages/index.page.ts
, which renders the homepage you saw earlier. Let’s add an /about page. Add a new file about.page.ts
to the pages directory and paste the code below in it:
import { Component } from "@angular/core";
import { RouterLink } from "@angular/router";
@Component({
standalone: true,
imports: [RouterLink],
template: `
<h1>About Us</h1>
<p>We are building a full-stack application with Analog!</p>
<a routerLink="/">Back to Home</a>
`,
})
export default class AboutPageComponent {}
Notice how we’re exporting the component as the default export. This is how Analog identifies the main component for each route. If your dev server is still running, navigate to localhost:5173/about to see it working.
There is more to file-based routing than I’d like to cover in this article. You can learn more about routing in the Analog documentation. It covers more about layout routes, grouping, etc.
Analog makes it easy to create API endpoints that can serve data to the application. These are defined in the src/server/routes
directory. API routes are also filesystem-based routing and are exposed under the default /api
prefix. Therefore, the file src/server/routes/users.ts
will be accessible under /api/users
. The prefix can be configured with the apiPrefix
parameter passed to the analog
vite plugin. For example:
// vite.config.ts
import analog from "@analogjs/platform";
export default defineConfig(({ mode }) => {
return {
plugins: [
analog({
apiPrefix: "services",
}),
],
};
});
Let’s make routes to fetch user data. Add a new file, users.ts
, to the src/server
directory and paste the code below in it:
// Sample user data
export const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
{ id: 4, name: "David" },
{ id: 5, name: "Eve" },
{ id: 6, name: "Frank" },
{ id: 7, name: "Grace" },
];
Now, let’s create a route to fetch all users. Add a new file index.get.ts
to the src/server/routes/v1/users
directory and paste the code below in it:
import { eventHandler } from "h3";
import { users } from "../../../users";
export default eventHandler(() => ({ users }));
This route returns all users. It exports a handler function that will process the user request and return a response. An event handler is a function that will be bound to a route and executed when the route is matched by the router.
Note: The
eventHandler
function provides a convenient way to create event handlers for HTTP requests. It’s part of theh3
package, which Analog (via Nitro) depends on.
Notice that we added a get
suffix to the filename. This is a convention that Analog uses to identify the HTTP method for the route, and it’s optional. When absent, the route will match all HTTP methods.
Let’s create a route to fetch a single user. Create a new file [id].get.ts
to the src/server/routes/v1/users
directory and paste the code below in it:
import { eventHandler, getRouterParam } from "h3";
import { users } from "../../../users";
export default eventHandler((event) => {
const id = getRouterParam(event, "id");
const user = users.find((user) => user.id === Number(id));
if (!user) {
return new Response("User not found", { status: 404 });
}
return user;
});
Here, we’re using the getRouterParam
function to get the id
parameter from the route. We then use the find
method to find the user with the given id. If the user is not found, we’re returning a 404 response.
You can test the API routes by running the dev server and navigating to localhost:5173/api/v1/users. You should see the list of users. You can also test the [id].get.ts
route by navigating to localhost:5173/api/v1/users/1. You should see the user with ID 1.
You can access cookies and add WebSocket support to your API routes, but that’s beyond the scope of this article. We will cover that in an upcoming article.
You can learn more about API routes in the documentation.
Server-side rendering (SSR) is a critical feature for web applications. It provides better SEO, faster initial page loads and improved user experience. Analog supports SSR, and it is enabled by default. This means that when you build your application, Analog will generate build artifacts that will be used to render it on the server. You’ll find them in the build/analog/server
directory.
You can opt out of SSR by setting the ssr
option to false
in your vite.config.ts
file.
import { defineConfig } from "vite";
import analog from "@analogjs/platform";
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
// ...other config
plugins: [analog({ ssr: false })],
}));
Some dependencies may need additional transforms to work for server-side rendering. You may need to add the package(s) to the ssr.noExternal
array in the Vite config if you receive errors related to them. For example:
import { defineConfig } from "vite";
import analog from "@analogjs/platform";
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
ssr: {
noExternal: [
"apollo-angular", // npm package import
"apollo-angular/**", // npm package import along with sub-packages
"@spartan-ng/**", // libs under the npmScope inside an Nx workspace
],
},
plugins: [analog({ ssr: true })],
}));
Analog supports both Server-Side Rendering (SSR) and Static Site Generation (SSG), giving you the flexibility to choose the right approach for each page in your application. You can use a Hybrid approach where some pages are pre-rendered at build time (SSG) and others are rendered on the server (SSR) at request time.
For SSG, Analog pre-renders pages at build time, creating static HTML files that can be served by any static file server. This is great for content that doesn’t change often. To enable SSG for a page, you can use the prerender
option in the analog()
plugin for Vite. For example:
import { defineConfig } from "vite";
import analog from "@analogjs/platform";
export default defineConfig(({ mode }) => ({
plugins: [
analog({
prerender: {
routes: async () => ["/", "/about", "/services"],
},
}),
],
}));
Analog is built on top of Vite, a modern frontend build tool that offers significant benefits. It has become the standard build tool for many frontend frameworks. It’s used in Remix, Solid Start, Tanstack Start and many more. You get the following features from Vite:
The integration with Vite means your development workflow is much faster and more enjoyable. You’ll also benefit from the extensive ecosystem of Vite plugins, which can be used to enhance your application’s functionality.
In this article, we’ve explored how Analog brings modern full-stack capabilities to the Angular ecosystem. We’ve covered:
Analog represents an exciting evolution for Angular development. It brings many of the developer experience improvements that have made other meta-frameworks so popular while still leveraging the robust architecture that Angular is known for. I didn’t go deep into the details of Analog’s features, but I hope this overview has piqued your interest. I’ll be releasing more content on Analog in the coming weeks, so stay tuned!
Head over to the Analog documentation to learn more.
Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.