Runable v1.0.0-alpha.20
Why Runable? Docs Modules Work with AI Blog About Changelog
  • 01Why Runable?
  • 02Docs
  • 03Modules
  • 04Work with AI
  • 05Blog
  • 06About
  • 07Changelog
Vue without a fixed server runtime.
Getting Started Structure Integrations Guide MCP API
  • Getting Started Structure Integrations Guide MCP API
  • Why Runable
  • Installation
  • Quick Start
  • Runable vs Nuxt
  • Configuration
  • Concepts

Quick Start

Create a Runable application with two pages, a layout, an API route, and server-rendered data.

Build a small application that combines automatic routing, a shared layout, and SSR data loading.

This page starts from the Express project created in Installation.

Add an API route

Replace server.ts with this example:

// server.ts
import Express from "express";
import { express } from "runable/adapters/express";

const server = Express();

server.get("/api/projects", (_req, res) => {
  res.json([
    { id: 1, name: "Documentation" },
    { id: 2, name: "Dashboard" },
  ]);
});

server.use(express());

server.listen(3000, () => {
  console.log("http://localhost:3000");
});

Your API remains a regular Express route. Runable does not move it into the frontend.

Create a layout

<!-- app/layouts/default.vue -->
<template>
  <div>
    <header>
      <strong>My application</strong>

      <nav>
        <RouterLink to="/">Home</RouterLink>
        <RouterLink to="/projects">Projects</RouterLink>
      </nav>
    </header>

    <main>
      <slot />
    </main>
  </div>
</template>

The default.vue layout wraps pages that do not explicitly request another layout.

Create the home page

<!-- app/pages/index.vue -->
<template>
  <section>
    <h1>Welcome</h1>
    <p>The Express backend and Vue application live in the same project.</p>
  </section>
</template>

app/pages/index.vue automatically maps to /.

Load data

Create a second page:

<!-- app/pages/projects.vue -->
<script setup lang="ts">
type Project = {
  id: number;
  name: string;
};

const { data: projects, pending, error, refresh } = await useAsyncData(
  "projects",
  async (signal) => {
    const response = await fetch("http://localhost:3000/api/projects", {
      signal,
    });

    if (!response.ok) {
      throw new Error("Unable to load projects");
    }

    return response.json() as Promise<Project[]>;
  },
);
</script>

<template>
  <section>
    <h1>Projects</h1>

    <p v-if="pending">Loading…</p>
    <p v-else-if="error">{{ error.message }}</p>

    <ul v-else>
      <li v-for="project in projects" :key="project.id">
        {{ project.name }}
      </li>
    </ul>

    <button type="button" @click="refresh">Refresh</button>
  </section>
</template>

useAsyncData() runs the fetch during server rendering. Runable embeds the result in the HTML and restores the cache on the client, so the browser does not immediately repeat the request during hydration.

One stable key per resource

The projects key identifies the cache entry and deduplicates simultaneous calls. Use a different key when request parameters change.

Observe automatic routing

Your directory now contains two routes:

app/pages/
├── index.vue       → /
└── projects.vue    → /projects

Add a file to app/pages/ to create a route. There is no route table to maintain.

What you just used

NeedRunable solution
Display several screensRouting based on app/pages/
Share navigationdefault.vue layout
Keep application routes/api/projects route in Express
Preload data during SSRuseAsyncData()
Avoid a second fetch on mountCache serialization and hydration
Next step

Compare this model with Nuxt in Runable vs Nuxt.

Report an issue Edit this page
Installation

Install Runable in an existing backend project and prepare a minimal Vue application with Express.

Runable vs Nuxt

Compare Runable and Nuxt by server runtime, backend integration, Vue conventions, and ecosystem maturity.

On this page

  • 1Add an API route
  • 2Create a layout
  • 3Create the home page
  • 4Load data
  • 5Observe automatic routing
  • 6What you just used
Runable

The Vue framework that brings productive conventions to any backend.

Product

  • Why Runable
  • Documentation
  • Installation
  • Integrations

Project

  • About
  • Blog
  • Changelog
  • Sponsor

Community

  • GitHub
  • Issues
  • Discussions
  • Bluesky

© 2026 Runable. Released under the MIT License.

Open source Built with Vue and Runable