part-13.jpg

Sails.js & Vue.js - Part 3

🛈

Excellent. The enchanted shipyard has provided us with a state-of-the-art vessel. Now it’s time to step inside and bring its instruments to life. We will begin with the most crucial room on any ship: the Chart Room.


Part 3: The Living Chart Room (Building with Inertia & Vue)

  • Current Rank: Lord High Admiral
  • Objective: To transform a static information page into a dynamic, data-driven Vue component using Inertia, learning to pass data from Sails, navigate between pages without reloads, and submit forms.

Chief Architect’s Briefing (Background and Theory)

Our inertia-flagship is ready. The core task now is to build out a feature. We will create a “Chart Room” where the captain can view a list of known destinations and plot new ones.

This process will demonstrate the complete, magical end-to-end flow of the Modern Monolith:

  1. A user will click a <Link> component on our homepage to navigate to /chartroom.
  2. Our Sails router (config/routes.js) will direct this request to a new ChartRoomController.
  3. The controller will use a Destination model to fetch all known destinations from the database.
  4. The controller will then pass this list of destinations as props to a Vue page component using res.inertia('ChartRoom', { destinations: allDestinations }).
  5. Our ChartRoom.vue component will receive the destinations array as a prop and use a v-for loop to display it.
  6. Inside the ChartRoom.vue component, we will build a form. When submitted, this form will use an Inertia helper (useForm) to POST data to a new Sails endpoint. This helper gracefully handles loading states and validation errors for us.
  7. The Sails controller action will process the form data, create a new Destination in the database, and then issue a res.redirect('/chartroom').
  8. Inertia intercepts this redirect, automatically re-fetches the props for /chartroom (which now include the new destination), and updates the page.

Notice: no manual fetch calls, no manual JSON parsing, no manual page refreshing. It’s a seamless conversation between the backend and frontend.


Key Concepts Checklist

  • defineProps: A Vue 3 <script setup> macro to declare the props a component expects to receive.
  • @inertiajs/vue3: The official Inertia Vue 3 adapter, which provides core components and helpers.
  • <Link> component: The Inertia replacement for <a> tags for client-side navigation.
  • useForm helper: An Inertia hook for wrapping form data and simplifying submissions, including managing loading, error, and success states.
  • Server-Side Redirects: How Sails can tell Inertia to navigate to a new page alter an action (like a form submission).

Mission Log: Quest - “Plotting the Course”

  • Task 1: Model the Destinations

    1. First, our Chart Room needs data. Let’s create a model for our destinations.

      sails generate model Destination name:string eta:string
      

      (ETA = Estimated Time of Arrival)

  • Task 2: Create the Controller & Route

    1. Generate the controller that will manage showing the chart room.

      sails generate controller ChartRoomController
      
    2. Open api/controllers/ChartRoomController.js and create a show action. This action finds all destination data and passes it to a Vue component named ChartRoom.

      module.exports = {
        show: async function (req, res) {
          const destinations = await Destination.find().sort('createdAt DESC');
          return res.inertia('ChartRoom', { destinations });
        },
      };
      
    3. Now, wire up a route in config/routes.js. Add this line:

      'GET /chartroom': 'ChartRoomController.show',
      
  • Task 3: Build the Chart Room Vue Component

    1. Create the page component file: assets/js/pages/ChartRoom.vue.
    2. Add the following code. This component defines destinations as a prop and then displays them in a list.

      <script setup>
      import { defineProps } from 'vue';
      
      defineProps({
        destinations: Array,
      });
      </script>
      
      <template>
        <div class="container">
          <h1>The Chart Room</h1>
          <p>A list of all known destinations in the fleet's database.</p>
      
          <div class="dest-list">
            <h2>Known Destinations</h2>
            <ul>
              <li v-for="dest in destinations" :key="dest.id">
                <strong>{{ dest.name }}</strong> (ETA: {{ dest.eta }})
              </li>
            </ul>
            <p v-if="!destinations.length">No destinations plotted yet.</p>
          </div>
        </div>
      </template>
      
  • Task 4: Create Navigation to the Chart Room

    1. We need a way to get to our new page. Open assets/js/pages/Homepage.vue.
    2. Import the <Link> component and add a link to /chartroom.

      <script setup>
      import { Link } from '@inertiajs/vue3'; // <-- IMPORT THIS
      </script>
      
      <template>
        <!-- ... existing homepage content ... -->
        <Link href="/chartroom" class="button-link">Enter the Chart Room</Link> <!-- ADD THIS -->
      </template>
      
      <style>
        .button-link { /* Add some basic styling */
          display: inline-block;
          padding: 10px 20px;
          margin-top: 20px;
          background-color: #007bff;
          color: white;
          text-decoration: none;
          border-radius: 5px;
        }
      </style>
      
  • Task 5: Test the Data Flow

    1. Lift your sails: sails lift.
    2. Visit http://localhost:1337. Click the “Enter the Chart Room” link.
    3. Observe: The URL changes to /chartroom and you see the new page without a full page refresh. It will say “No destinations plotted yet.” This confirms the link, controller, and component are all wired up correctly.
  • Task 6: Add the Form to Plot New Courses

    1. Now for the interactive part. Let’s add a form to assets/js/pages/ChartRoom.vue.
    2. Update the <script setup> section to import useForm.
    3. Add the form and its submission logic.

      // In assets/js/pages/ChartRoom.vue
      <script setup>
      import { defineProps } from 'vue';
      import { useForm } from '@inertiajs/vue3'; // <-- Import useForm
      
      defineProps({
        destinations: Array,
      });
      
      // Create a form object that wraps our input data
      const form = useForm({
        name: '',
        eta: '',
      });
      
      // The function to call on form submission
      const plotCourse = () => {
        form.post('/destinations', {
          // This runs after a successful submission
          onSuccess: () => form.reset('name', 'eta'),
        });
      };
      </script>
      
      <template>
        <!-- ... The h1, p, and destination list from before ... -->
      
        <div class="new-dest-form">
          <h2>Plot a New Course</h2>
          <form @submit.prevent="plotCourse">
            <div>
              <label for="name">Destination Name:</label>
              <input id="name" type="text" v-model="form.name" required>
            </div>
            <div>
              <label for="eta">ETA:</label>
              <input id="eta" type="text" v-model="form.eta" required>
            </div>
            <button type="submit" :disabled="form.processing">
              {{ form.processing ? 'Plotting...' : 'Plot Course' }}
            </button>
          </form>
        </div>
      </template>
      
  • Task 7: Create the Form-Handling Endpoint

    1. The form POSTs to /destinations. Let’s create that route and controller logic.
    2. In config/routes.js, add the POST route:

      'POST /destinations': 'ChartRoomController.createDestination',
      
    3. In api/controllers/ChartRoomController.js, add the createDestination action:

      module.exports = {
        show: async function (req, res) {
          // ... same as before
        },
      
        createDestination: async function(req, res) {
          await Destination.create({
            name: req.body.name,
            eta: req.body.eta,
          });
          // This redirect is caught by Inertia, which then reloads the page's props!
          return res.redirect('/chartroom');
        },
      };
      
  • Task 8: Final Test
    1. Relaunch your app (sails lift).
    2. Go to the Chart Room. Fill out the form (e.g., Name: “Tortuga”, ETA: “3 days”) and click “Plot Course”.
    3. Witness the magic: The form submits, the button briefly says “Plotting…”, and then the new destination instantly appears in the “Known Destinations” list above, without a full page reload.

Mission Debrief (Review & Outcomes)

Outstanding, Admiral. The Chart Room is now a living instrument. You have mastered the fundamental workflow of a modern Sails + Inertia application. You’ve passed data as props, navigated with <Link>, and handled form submissions with useForm, all while keeping your controller logic clean and familiar.

You can see how this pattern removes immense complexity. The front-end doesn’t need to know how to re-fetch the list; it just submits a form. The back-end doesn’t need to return special JSON; it just creates data and issues a standard redirect. Inertia handles the complex conversation between them, letting each side focus on what it does best.


Rewards

  • +600 Doubloons
  • Achievement Unlocked: You have built a fully functional, data-driven, interactive SPA page.
    • Badge Earned: The Living Cartographer 🗺️✨