The fleet is assembled, the ships are enchanted, and the Chart Room is alive with data. But a true commander needs to see the entire theatre of operations at a single glance. Our components can communicate with the server, but they cannot yet speak to each other. It is time to establish a fleet-wide comms channel. It is time for the Admiral’s Spyglass.
Part 4: The Admiral’s Spyglass (Advanced State & Capstone)
- Current Rank: Lord High Admiral
- Objective: To master global state management by integrating Pinia, allowing disconnected Vue components to share data. We will complete our journey by building a real-time fleet dashboard that updates instantly across the entire application using Sails Sockets.
Chief Architect’s Briefing (Background and Theory)
Our current architecture is powerful. Inertia passes data from a Sails controller directly to a page component as props. But what happens when a different, separate component needs that same data?
Imagine a <Navbar> component that needs to display the number of ships in the fleet, and a <FleetOverview> component that needs to list them. The data (ships) is passed to our main Dashboard.vue page. How do we get it to both the navbar and the overview without complex “prop drilling” (passing data down through layers of components)?
This is the role of a global state management library. Pinia is the current official, lightweight, and intuitive choice for Vue 3 frontendmasters.com.
Think of a Pinia “store” as the ship’s Quartermaster. Instead of every crew member having their own small inventory list, there is one central Quartermaster who holds the master list for the entire ship. Any crew member, from any part of the ship, can ask the Quartermaster for information or ask them to update the master list.
Here’s how it works with Inertia:
- Initial Load: Our Sails controller fetches the fleet data and passes it as a prop to our main page component (
Dashboard.vue) viares.inertia(). - Populate the Store: The first thing
Dashboard.vuedoes with these props is hand them off to the Pinia store (the Quartermaster) to initialize the central state. - Component Access: Now, any other component in our app (
Navbar.vue,FleetStatus.vue, etc.) can completely ignore props and instead directly ask the Pinia store for the data it needs. - Real-Time Updates: When our Sails app receives new information (e.g., a new ship is commissioned), it can use Sails Sockets to
blasta message to all connected clients. A listener in our app will hear this message and tell the Pinia store to update its state. Because the store is reactive, every single component that uses its data will update automatically and instantly.
This creates our capstone: a true real-time Single-Page Application.
Key Concepts Checklist
Pinia: The official global state management library for Vue 3.Store: A Pinia container for state, getters, and actions.defineStore: The function used to create a new store.State: The raw, reactive data in a store (e.g., the array of ship objects).Getters: Computed values derived from state (e.g., the total count of ships).Actions: Functions that can modify the state (e.g.,addShip(),setShips()).Sails Sockets: The real-time communication layer built into Sails.sails.sockets.blast(): A Sails method to broadcast a message to all connected clients.
Mission Log: Quest - “The Fleet Command Dashboard”
We will build on our inertia-flagship project.
Task 1: Recruit the Quartermaster (Install Pinia)
In your
inertia-flagshipterminal, add Pinia to the project.npm install pinia
Task 2: Swear in the Quartermaster (Configure Pinia)
We need to tell our Vue app to use Pinia. Open
assets/js/app.js.// assets/js/app.js import '../css/app.css'; import { createApp, h } from 'vue'; import { createInertiaApp } from '@inertiajs/vue3'; import { createPinia } from 'pinia'; // <-- IMPORT THIS const pinia = createPinia(); // <-- CREATE THE INSTANCE createInertiaApp({ /* ... existing config ... */ setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(pinia) // <-- TELL VUE TO USE IT .mount(el); }, });
Task 3: Create the Master Fleet Roster (The Pinia Store)
- Create a new directory:
assets/js/stores/. Inside, create
fleetStore.js. This will be our single source of truth for fleet data.// assets/js/stores/fleetStore.js import { defineStore } from 'pinia'; export const useFleetStore = defineStore('fleet', { state: () => ({ ships: [], }), getters: { shipCount: (state) => state.ships.length, isFleetReady: (state) => state.ships.length > 0, }, actions: { setShips(initialShips) { this.ships = initialShips; }, addShip(newShip) { this.ships.unshift(newShip); // Add new ships to the top } }, });
- Create a new directory:
Task 4: Prepare the Backend Data & Real-Time Broadcast
Create a model, controller, and route for a list of ships.
sails generate model Ship name:string class:string sails generate controller DashboardControllerIn
config/routes.js, add the dashboard routes:'GET /dashboard': 'DashboardController.show', 'POST /ships': 'DashboardController.createShip',In
api/controllers/DashboardController.js, add the logic. Note thesails.sockets.blastcall.module.exports = { show: async function (req, res) { if (req.isSocket) { // Join a room to hear about new ships sails.sockets.join(req, 'fleet-dashboard'); } const ships = await Ship.find().sort('createdAt DESC'); return res.inertia('Dashboard', { ships }); }, createShip: async function (req, res) { const newShip = await Ship.create({ name: req.body.name, class: req.body.class }).fetch(); // Broadcast the new ship data to all listening clients! sails.sockets.blast('ship_added', { newShip }); return res.ok(); // Just send a 200 OK, no redirect needed for API-like calls } };
Task 5: Build the Dashboard and Components
Create the main page component
assets/js/pages/Dashboard.vue. Its only job is to initialize the store.<script setup> import { onMounted } from 'vue'; import { useFleetStore } from '../stores/fleetStore'; import FleetStatus from '../components/FleetStatus.vue'; import ShipList from '../components/ShipList.vue'; // Get props from Inertia const props = defineProps({ ships: Array, }); // Get a reference to our store const fleetStore = useFleetStore(); // On initial load, populate the store with the data from the server fleetStore.setShips(props.ships); // Listen for real-time updates onMounted(() => { io.socket.on('ship_added', ({ newShip }) => { console.log('New ship received via socket!'); fleetStore.addShip(newShip); }); }); </script> <template> <h1>Fleet Command Dashboard</h1> <FleetStatus /> <ShipList /> </template>Create
assets/js/components/FleetStatus.vue. This component knows nothing about props; it only knows about the store.<script setup> import { useFleetStore } from '../stores/fleetStore'; const fleetStore = useFleetStore(); </script> <template> <div class="status-box"> <h3>Fleet Status</h3> <p>Total Ships: <strong>{{ fleetStore.shipCount }}</strong></p> <p v-if="fleetStore.isFleetReady">Status: <span class="ready">Ready for deployment!</span></p> <p v-else>Status: <span class="not-ready">No ships in fleet.</span></p> </div> </template>Create
assets/js/components/ShipList.vue, which also uses the store.<script setup> import { useFleetStore } from '../stores/fleetStore'; const fleetStore = useFleetStore(); </script> <template> <div class="list-box"> <h2>Ship Roster</h2> <ul> <li v-for="ship in fleetStore.ships" :key="ship.id"> {{ ship.name }} ({{ ship.class }}) </li> </ul> </div> </template>
Task 6: Test the Final System
sails liftyour application. Navigate tohttp://localhost:1337/dashboard. You should see the dashboard with “Total Ships: 0”.- Open a second browser window or tab and navigate to the same page.
In a terminal, use
curl(or Postman/Insomnia) to simulate commissioning a new ship:curl -X POST http://localhost:1337/ships -d "name=The Endeavour&class=Frigate"- Witness the final magic: Instantly, without a page refresh, both browser windows will update. “Total Ships” will become 1, and “The Endeavour” will appear in the roster.
Mission Debrief (Review & Outcomes)
Mission Accomplished, Lord High Admiral.
You have reached the pinnacle of modern web application development. Your flagship is not just a collection of pages; it is a living, breathing application. Data flows seamlessly from the Sails database, through Inertia into a central Pinia store, and is shared across your entire Vue front-end. With Sails Sockets, that data is now updated in real-time, providing an instantaneous experience for all users.
You have mastered the entire process, from laying the first keel with sails new to commanding a real-time fleet with Pinia and Sockets. You are no longer just a captain; you are the architect and commander of a truly modern armada.
The seas of development are vast, but you now possess the charts, the tools, and the command experience to sail anywhere.
Rewards
- +1000 Doubloons
- Achievement Unlocked: You have built a full-stack, globally-managed, real-time single-page application.
- Final Badge Earned:
The Grand Fleet Admiral🎖️🌟
- Final Badge Earned: