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:
- A user will click a
<Link>component on our homepage to navigate to/chartroom. - Our Sails router (
config/routes.js) will direct this request to a newChartRoomController. - The controller will use a
Destinationmodel to fetch all known destinations from the database. - The controller will then pass this list of destinations as props to a Vue page component using
res.inertia('ChartRoom', { destinations: allDestinations }). - Our
ChartRoom.vuecomponent will receive thedestinationsarray as a prop and use av-forloop to display it. - Inside the
ChartRoom.vuecomponent, we will build a form. When submitted, this form will use an Inertia helper (useForm) toPOSTdata to a new Sails endpoint. This helper gracefully handles loading states and validation errors for us. - The Sails controller action will process the form data, create a new
Destinationin the database, and then issue ares.redirect('/chartroom'). - 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.useFormhelper: 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
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
Generate the controller that will manage showing the chart room.
sails generate controller ChartRoomControllerOpen
api/controllers/ChartRoomController.jsand create ashowaction. This action finds all destination data and passes it to a Vue component namedChartRoom.module.exports = { show: async function (req, res) { const destinations = await Destination.find().sort('createdAt DESC'); return res.inertia('ChartRoom', { destinations }); }, };Now, wire up a route in
config/routes.js. Add this line:'GET /chartroom': 'ChartRoomController.show',
Task 3: Build the Chart Room Vue Component
- Create the page component file:
assets/js/pages/ChartRoom.vue. Add the following code. This component defines
destinationsas 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>
- Create the page component file:
Task 4: Create Navigation to the Chart Room
- We need a way to get to our new page. Open
assets/js/pages/Homepage.vue. 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>
- We need a way to get to our new page. Open
Task 5: Test the Data Flow
- Lift your sails:
sails lift. - Visit
http://localhost:1337. Click the “Enter the Chart Room” link. - Observe: The URL changes to
/chartroomand 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.
- Lift your sails:
Task 6: Add the Form to Plot New Courses
- Now for the interactive part. Let’s add a form to
assets/js/pages/ChartRoom.vue. - Update the
<script setup>section to importuseForm. 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>
- Now for the interactive part. Let’s add a form to
Task 7: Create the Form-Handling Endpoint
- The form
POSTs to/destinations. Let’s create that route and controller logic. In
config/routes.js, add thePOSTroute:'POST /destinations': 'ChartRoomController.createDestination',In
api/controllers/ChartRoomController.js, add thecreateDestinationaction: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'); }, };
- The form
- Task 8: Final Test
- Relaunch your app (
sails lift). - Go to the Chart Room. Fill out the form (e.g., Name: “Tortuga”, ETA: “3 days”) and click “Plot Course”.
- 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.
- Relaunch your app (
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🗺️✨
- Badge Earned: