Skip to content

Vue the Mirage from this angle!

Fire the base of Vue.js! - 2 Part Series

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Despite being designed in the late 1970’s, the lightweight Mirage jet fighter is the topic of today’s article!

You might assume that I’m kidding, but I’m not!

We are going to look at Mirage from a different angle in this article. The Vue.js angle!

In the early stages of full-stack app development, everything is subject to change. The database structure is not complete, Web APIs are still under construction, and the front-end development is on-going. Building the layout, and writing all HTML/CSS can be a much faster process when you have a specific design in mind.

At some point, it's not unusual for the front-end developers to be stalled or blocked by the incomplete back-end APIs. They need to populate some dynamic sections based on data eventually coming through from the backend.

Solutions for this problem, which I’ve employed in the past, include using mock files or in-memory databases to see how things will pan out. The down side? It does come with its hiccups.

That’s why Mirage.js exists! Mirage.js is a client-side server that embeds itself into the app. It handles all of the API endpoints calls without the need for a real backend server.

This article will introduce to you Mirage.js as we'll build a Note Writer app in Vue.js.

The source code of this article can be cloned from this GitHub repo: Notes Writer.

How can Mirage.js help?

Developing an app with the backend API development lagging can bring things to a halt for the front-end team. That’s why we tend to use workarounds like reading from mock files, or even building fake in-memory services to simulate API responses. This enables us to build the front-end, display and render any dynamic data, and get a full picture of what the app looks like once it’s done.

For instance, each mock file mimics a separate database entity. You then add client-side Web services to read the mock files, and make their content available to the app. When the backend APIs are ready, you simply visit these services, and amend the code to interact with the real API endpoints instead. That’s easy, but time is wasted replacing the mock Web services with the real ones.

This approach creates extra work for the developer, and extensive time later coding the services to communicate with the real API endpoints.

Mirage.js offers a simple solution for this: an approach that requires no code changes at all when it’s time to use the real API endpoints.

With Mirage.js, you create an instance of a Server object. This object is the window through which Mirage.js interacts with your application and handles all API endpoints.

Routes and Route Handlers

Inside this Server object instance, you define a set of routes and route handlers. A route and a route handler look like this:

this.get("/notes", () => {
	return [
		{ id: "1", body: "The first note body goes here", title: "First Note" },
		{ id: "2", body: "The second note body goes here", title: "Second Note" },
		{ id: "3", body: "The third note body goes here", title: "Third Note" }
	];
})

For a moment, you feel you are writing some Node.js backend code!

This snippet defines a GET route having an endpoint of /notes. The second parameter of the get() route function defines the route handler, that is, the code that runs when the route /notes is called. The route handler returns notes data.

You can define more routes and route handlers as your app needs. For instance, you can define all CRUD routes including POSTING a new note, PATCHING an existing note, and DELETING an existing note.

Mirage.js supports all HTTP Verbs. In addition, to simulate a real API response delay, Mirage.js allows you to pass, as a third argument to the route function, a timing object that specifies the amount of delay in milliseconds to incur before returning a response to the client-side app. You can define this timing object as:

this.get("/notes", () => {
	return [
		{ id: "1", body: "The first note body goes here", title: "First Note" },
		{ id: "2", body: "The second note body goes here", title: "Second Note" },
		{ id: "3", body: "The third note body goes here", title: "Third Note" }
	];
}, { timing: 5000 })

Mirage.js Database and ORM

So far, we introduced the static route handlers. The route handler statically defines the set of data to return every time the route is requested.

What if you want the ability to add new notes? Delete existing? Edit existing? Then you can use the dynamic route handlers supported by Mirage.js.

In order to support dynamic data, Mirage.js offers an in-memory database that is accessed by the server.db object instance.

Along with the database support, Mirage.js comes with an ORM. You will spend most of your time working with the ORM instead of accessing the database directly. With the ORM, you can create, update, delete, and query for data inside the database.

For the ORM/Database integration to work, you should define your models inside the Server object. The models represent the entities you are dealing with within your application. Also, models are registered by the ORM that use them to shape the data returned from the database.

To define a model in your Server object, you:

import { Server, Model } from "miragejs";

new Server({
	models: {	
		note: Model
	},

	routes () {
		this.namespace = "api";

		this.get("/notes", schema => {
			return schema.notes.all();
		})
	}
});

You wrap all your models inside the models object. Any model you define is of type Model class.

Internally, Mirage.js stores a collection of notes. By default, it pluralizes the model name you have given. Now you can add/edit/remove notes from this collection.

Then, you refactor the route handler to make use of the schema object. The schema object is provided as the first parameter on the route handler. It represents the ORM gateway to access all the collections stored inside the database.

The schema object adds functionality to the collections. For example, it adds a set of functions that are essential to retrieve, edit, create, and delete data from the collections.

schema.notes.create();
schema.notes.all();
schema.notes.find();
schema.notes.findBy();
schema.notes.where();
schema.notes.destory();

By using the ORM API, you let Mirage.js shape and use internal serializers to format your data as JSON, or any other supported format, and make your data ready to be transmitted to the front-end app.

A second parameter that a route handler accepts is the request parameter. The request parameter represents the current HTTP Request object.

To complete this section, it’s worth mentioning the seeds() function that you can use to seed your database with some initial data:

import { Server, Model } from "miragejs";

new Server({
	models: {	
		note: Model
	},

	routes () {
		this.namespace = "api";

		this.get("/notes", schema => {
			return schema.notes.all();
		})
	},

	seeds (server) {
		server.create("note", { body: "# An h1 header", title: "# An h1 header" });
		server.create("note", { body: "## An h2 header", title: "## An h2 header" });
		server.create("note", { body: "### An h3 header", title: "### An h3 header" });
		server.create("note", { body: "#### An h4 header", title: "#### An h4 header" });
		server.create("note", { body: "##### An h5 header", title: "##### An h5 header" });
		server.create("note", { body: "###### An h6 header", title: "###### An h6 header" });
	}
});

The seeds() function accepts the server object as an input parameter. You make use of the server.create() function to seed initial data in the database.

One important aspect of Mirage.js is that it auto-generates values for the id field for each and every record in the notes collection.

The Mirage.js team has done a great job of documenting this product. You can consult the documentation website to read more about Mirage.js.

Demo

Now that we've laid down the basics of Mirage.js, let’s start using it to build a Vue.js Notes Writer app.

Create a new Vue.js app

To start, make sure you install the latest version of the Vue CLI by running this command:

npm install -g @vue/cli

The command downloads and installs the latest bits of the Vue CLI on your computer.

To verify the version of the Vue CLI installed, you can run this command:

vue --version

The command displays:

@vue/cli 4.1.1

To create a new Vue.js app, run the following command:

vue create notes-writer-app

This command triggers a set of questions to shape the features to be included in the new app. You are free to choose the options that suit your app. In my case, I’ve chosen the following:

Vue CLI v4.1.1
? Please pick a preset:
  default (babel, eslint)
❯ Manually select features

? Check the features needed for your project:
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
❯◯ Unit Testing
 ◯ E2E Testing

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default)
:
  Sass/SCSS (with dart-sass)
❯ Sass/SCSS (with node-sass)
  Less
  Stylus

? Pick a linter / formatter config:
  ESLint with error prevention only
  ESLint + Airbnb config
❯ ESLint + Standard config
  ESLint + Prettier

? Pick additional lint features:
❯◯ Lint on save
 ◉ Lint and fix on commit

? Where do you prefer placing config for Babel, ESLint, etc.?
  In dedicated config files
❯ In package.json

The CLI takes a few seconds to download and install all the bits. Once done, you can navigate to the newly created app folder and run the app:

cd notes-writer-app
npm run serve

Now that the Vue.js app is created, let’s install the required NPM packages for this app.

Install NPM dependencies

A few additional NPM packages are required by the Notes Writer app. To install them, follow the steps below:

npm install --save axios

This command installs the axios library to allow us to communicate with a backend API using HTTP Requests.

npm install --save lodash

This command installs the lodash library. This library gives us a handful set of JavaScript functions to perform complex tasks.

npm install --save marked

The command above installs the marked library. This library is used to convert Markdown text into HTML content. The Notes Writer app allows the user to write his/her notes using Markdown text. With the help of this library, the app shows the converted content in HTML right away.

npm install --save miragejs

The command above installs the miragejs library. This library will be used to build a client-side server to handle backend API requests.

One more dev-dependency is required since we will be using SASS is the sass library itself. You can install it by running:

npm install --save-dev sass

Those are all the NPM packages we need.

Let’s move on and introduce the Notes Writer app.

Introduce the UI

Before we start building the Notes Writer app, let me share the final app, and what it looks like when running it in a browser:

Screenshot 2019-12-07 at 10.16.31 PM

The app is split into two main sections:

  • Notes List: This section lists all the saved notes in the app.
  • Notes Editor: This section provides an editor to input the note content (Markdown or normal text). In addition, it displays a live preview of whatever text is typed inside the editor.

When the user starts typing, the Save and Clear buttons appear. The Save button is used to save the new or existing notes, while the Clear button clears the editor and disregards any text inside the editor.

The user can click a saved note in order to edit its content. When an existing note is being edited, an additional button named Delete appears to allow the user to delete an existing note.

Let’s start building this app!

Build the UI

Let’s start by building the Notes component. The template of this component is as follows:

<template>
  <div class="notes-container">
    <NotesList
      :notes="notes"
      @set-note="setNote"
      class="notes-container__list"
    ></NotesList>
    <NotesCreate
      :note="currentNote"
      @save-note="saveNote"
      @set-note="setNote"
      @delete-note="deleteNote"
      class="notes-container__create"
    ></NotesCreate>
  </div>
</template>

The template above embeds two other components:

  • NotesList component. This component receives a collection of note records as input.
  • NotesCreate component. This component receives the currently active note (being edited or viewed) as input.

The NotesList component exposes an event set-note that is emitted when the user clicks on an existing note to read or edit.

The NotesCreate component exposes a set of events:

  • save-note, emitted when the user clicks on the Save button to save a note.
  • set-note, emitted when the user starts typing a new note. The app employs a mechanism that keeps track of what the user is typing, and emits the set-note event. This event is handled by the Vuex Store to save the contents of the current note in a temporary placeholder inside the Store. In addition, this event is emitted when the user clicks on the Clear button to clear the editor.
  • delete-note emitted when the user clicks on the Delete button to delete an existing note.

The Notes component handles all the events emitted by its children components, and redirects them to the Vuex Store, where they are handled in one centralized place.

This component and the rest of the components use scoped SCSS in their templates:

<style lang="scss" scoped>
	@import "@/styles/components/notes.scss";
</style>

I’ve placed all the SCSS files inside a single /src/styles folder with the following structure:

  • components folder holding all the SCSS files for all components.
  • _variables.scss file to hold all SCSS variables used in the app
  • global.scss file to hold a reference to the _variables.scss file or any other SCSS that needs to be shared among the components style sheet files.

You may add additional subfolders in your apps depending on the need and scenario at hand.

In order to make the global.scss available to all the components in the app you need to create a new file (or modify the exising one) at the root of the project folder named vue.config.js and paste the following content inside it:

// vue.config.js
module.exports = {
  css: {
    loaderOptions: {
      sass: {
        prependData: `@import "@/styles/global.scss";`
      }
    }
  }
}

The global.scss file will be prepended to any other SCSS files in the app and will be available everywhere.

The NotesList component template is as follows:

<template>
  <div class="notes">
    <div class="notes__items">
      <Note
        v-for="(note, index) in notes"
        :key="index"
        v-bind="note"
        class="notes__items__item"
        @set-note="setNote(note)"
      ></Note>
    </div>
  </div>
</template>

This component iterates over the collection of note records and renders each note inside its own NoteItem component. As well, it handles the set-note event that is triggered by the NoteItem component.

The NoteItem component template is as follows:

<template>
  <div
    class="notes__item"
    @click.prevent="setNote"
  >
    <h4>{{ title }}</h4>
    <p class="notes__item__body">{{ body }}</p>
  </div>
</template>

The component displays the Note title and body.

The NotesCreate component template is as follows:

<template>
  <div class="editor__md">
    <div class="editor">
      <div v-if="showControls">
        <button
          class="btn btn-new-note"
          @click="saveNewNote"
        >Save</button>
        <button
          class="btn btn-clear-note"
          @click="resetNote"
        >Clear</button>
        <button
          class="btn btn-delete-note"
          v-if="showDeleteBtn"
          @click="deleteNote"
        >Delete</button>
      </div>
      <textarea
        name="markdown"
        :value="currentNote"
        @input="onNoteChanged"
        placeholder="Type your note here ..."
      ></textarea>
    </div>
    <div class="editor__compiled-md">
      <div v-html="compiledMarkdown"></div>
    </div>
  </div>
</template>

This template is a bit more involved. It starts by displaying the Control Buttons. Then it displays the textarea that’s used as an editor. Finally, it displays the compiled Markdown to the right side of the editor.

The Control buttons are shown based on the showControls computed property that’s defined as follows:

currentNote () {
	return this.note && this.note.body
},
showControls () {
  return !!this.currentNote
},

If there is a current note (whether a new one or an existing one selected) to show the Controls buttons.

As for the Delete button, it’s visibility is controlled by the showDeleteBtn button defined as follows:

showDeleteBtn () {
	return !!this.note.id
},

Show the Delete button only when an existing note is being edited.

The rest of the UI files in the app are related to styling that you can check and go through by visiting the GitHub repo of this app.

Build the Server with Mirage.js

Open the main.js file, and add the following just before the instantiation of the main Vue instance:

import { Server, Model } from 'miragejs'

/* eslint-disable no-new */
new Server({
	models: {
		note: Model
	},

	routes () {
		this.namespace = 'api'

		this.get('/notes', schema => {
		  return schema.notes.all()
		})

		this.post('/notes', (schema, request) => {
		  let attrs = JSON.parse(request.requestBody)
		  let newNote = schema.notes.create(attrs)
		  return schema.notes.find(newNote.id)
		})

		this.patch('/notes/:id', (schema, request) => {
		  let newAttrs = JSON.parse(request.requestBody)
		  let id = request.params.id
		  let note = schema.notes.find(id)

		  return note.update(newAttrs)
		})

		this.delete('/notes/:id', (schema, request) => {
		  let id = request.params.id
		  return schema.notes.find(id).destroy()
		})
	},

	seeds (server) {
		server.create('note', { body: '# An h1 header', title: '# An h1 header' })
		server.create('note', { body: '## An h2 header', title: '## An h2 header' })
		server.create('note', { body: '### An h3 header', title: '### An h3 header' })
		server.create('note', { body: '#### An h4 header', title: '#### An h4 header' })
		server.create('note', { body: '##### An h5 header', title: '##### An h5 header' })
		server.create('note', { body: '###### An h6 header', title: '###### An h6 header' })
	}
});

The server instance defines the CRUD routes and route handlers. For instance, the post() route handler parses the request.requestBody to access the payload of this post request. It then uses the ORM schema object to create a new note record. Finally, it uses the ORM schema object to find the newly created note, and returns it to the front-end app.

The server makes use of the seeds() function to initialize the database with some preliminary set of note records.

The routes() function sets the namespace to a value of api. Consequently, this means that any route URL will be suffixed with the /api URL segment. To get all notes stored in the database, you issue a GET request to /api/notes.

Now that the Mirage.js backend server is ready, let’s move on and build the Vuex Store.

Build the Vuex Store

The Store defines the following state:

state: {
	notesList: [],
	note: {}
},

It defines the notesList array to hold the list of all notes in the app, and defines the note object to hold the currently selected or newly created note.

The Store defines a set of actions:

async getNotesList ({ commit }) {
  let notes = [];

	await axios.get('/api/notes')
	.then(response => {
		notes = response.data.notes
	});

	commit('setNotesList', notes)
},

This action issues a GET request to /api/notes to retrieve all notes stored in the database. It then commits the notes into the store by calling the setNotesList mutation.

The setNotesList mutation is defined as follows:

setNotesList (state, notes) {
	state.notesList = notes
},

Another important action is the setNote action defined as:

setNote ({ commit }, { id = '', body = '' } = {}) {
	commit('setNote', { id, body })
},

This action is triggered when the user selects an existing note. Also, it is being triggered while the user is editing an existing note or creating a new note. It receives, as input, the id and body of the note. It commits the data into the store by calling the setNote mutation.

The setNote mutation is defined as:

setNote (state, { id, body }) {
	let note = {}

	if (id) {
		note = state.notesList.find(note => note.id === id)
		const newNoteBody = body || note.body

		note = { ...note, body: newNoteBody, title: newNoteBody.substring(0, 20) }
	} else if (body) {	
		note = { body, title: body.substring(0, 20) }
	}

	state.note = note
},

If the id input parameter is valid, it retrieves the existing note from the state.notesList array, and updates the content of the existing note. Otherwise, this is a new note. The code checks if the user has typed any content. If it does, it creates a new note record.

Finally, it sets the state.note to either the existing note or to the newly created note. This also covers the case in which the user clears the content of the editor, and resets the state.note object.

Another action that’s defined by the Store is the deleteNote action, which is defined as:

async deleteNote ({ commit, state }) {
	let id = (state.note && state.note.id)

	if (id) {
		let url = `/api/notes/${state.note.id}`
		await axios.delete(url)
	}

	commit('deleteNote', { id })
},

The action retrieves the note ID of the note currently stored inside the state.note object. If one exists, this means that there is an existing note that’s being edited right now. It then issues a DELETE Http Request to remove this note. Finally, it commits the results into the Store by calling the deleteNote mutation.

The deleteNote mutation is defined as follows:

deleteNote (state, { id }) {
	if (id) {
		state.notesList = state.notesList.filter(n => n.id !== id)
	}

	state.note = null
},

The deleteNote mutation filters out the deleted note from the state.notesList, and resets the existing state.note object.

The last action defined by the Store is the saveNote action that’s defined as:

async saveNote ({ commit, state }) {
	let note = {}

	let url = state.note.id ? `/api/notes/${state.note.id}` : '/api/notes'
	let method = state.note.id ? 'patch' : 'post'

	await axios({
		method,
		url,
		data: state.note
	}).then(response => {
		note = response.data.note
	})

	commit('saveNote', note)
}

This action checks if the currently edited note is an existing note or a new one. Accordingly, it sends a POST or PATCH request to the backend server to either create the new note or update an existing one. Finally, it commits the results into the Store by calling the saveNote mutation.

The saveNote mutation is defined as follows:

saveNote (state, note) {
	const notePosition = state.notesList.findIndex(n => n.id === note.id)

	if (notePosition < 0) {
		state.notesList.push(note)
	} else {
		state.notesList.splice(notePosition, 1, note)
	}

	state.note = null
}

The mutation above adds, or amends, a note inside the state.notesList to properly sync with the backend database.

Finally, the Store defines two getters to allow components to retrieve data from the state as:

getters: {
	notes: state => state.notesList,
	currentNote: state => state.note
},

That’s all that we have for the Store for the time being. Let’s see how the components make use of this Store in the next section.

Integrate the Store in the app

The components NoteItem, NotesList, and NotesCreate communicate with the Notes component via the this.$emit() event bus. And in turn, the Notes component communicates with the Store via Getters and Actions.

The Notes component defines the following code:

computed: {
	...mapGetters(['notes', 'currentNote'])
},
mounted () {
	this.getNotesList()
},
methods: {
	...mapActions(['getNotesList', 'saveNote', 'setNote', 'deleteNote'])
}

Inside the computed property, it imports the notes and currentNote Store getters. These getters are now available as computed properties inside this component. Inside the methods section, it imports the Store actions: getNotesList, saveNote, setNote, and deleteNote. These actions become methods defined in this component, and can be called like any other method defined.

The mounted lifecycle hook calls the getNotesList() action to retrieve the list of notes from the backend API, and fill the Store with the data.

<NotesCreate
	:note="currentNote"
	@save-note="saveNote"
	@set-note="setNote"
	@delete-note="deleteNote"
	class="notes-container__create"
></NotesCreate>

Notice how the events emitted by the NotesCreate component are bound directly to the methods (actions mapped to method names inside the component). When the user clicks the Save button, the action method saveNote is triggered automatically inside the Store.

The same applies to the rest of the actions in this component.

Run the app

Run the following command and start playing around with the app:

npm run serve

The app runs on the port 8080, and can be accessed by visiting the following URL: http://localhost:8080.

Start typing text inside the editor. You can use Markdown text. Notice how the compiled Markdown text is displayed on the right side of the editor. You may click the Save button to create a new note, or clear the editor. You can also click an existing note to load its content inside the editor.

Conclusion

This article demonstrates how you can continue working on the front-end side of your application while the backend is being built, especially during the early stages of development.

In future installments, I will be showing you how to integrate this app with Google Firebase to store the data, and authenticate users. You will be able to login to the app and manage your own set of notes.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Understanding Vue.js's <Suspense> and Async Components cover image

Understanding Vue.js's <Suspense> and Async Components

In this blog post, we will delve into how and async components work, their benefits, and practical implementation strategies to make your Vue.js applications more efficient and user-friendly. Without further ado, let’s get started! Suspense Let's kick off by explaining what Suspense components are. They are a new component that helps manage how your application handles components that need to await for some async resource to resolve, like fetching data from a server, waiting for images to load, or any other task that might take some time to complete before they can be properly rendered. Imagine you're building a web page that needs to load data from a server, and you have 2 components that fetch the data you need as they will show different things. Typically, you might see a loading spinner or a skeleton while the data is being fetched. Suspense components make it easier to handle these scenarios. Instead of manually managing loading states and error messages for each component that needs to fetch data, Suspense components let you wrap all these components together. Inside this wrapper, you can define: 1. What to show while the data is loading (like a loading spinner). 2. The actual content that should be displayed once the data is successfully fetched. This way, Vue Suspense simplifies the process of handling asynchronous operations (like data fetching) and improves the user (and the developer) experience by providing a more seamless and integrated way to show loading states and handle errors. There are two types of async dependencies that can wait on: - Components with an async setup() hook. This includes components using with top-level await expressions. *Note: These can only be used within a component.* - Async Components. Async components Vue's asynchronous components are like a smart loading system for your web app. Imagine your app as a big puzzle. Normally, you'd put together all the pieces at once, which can take time. But what if some pieces aren't needed right away? Asynchronous components help with this. Here's how they work: - Load Only What's Needed: Just like only picking up puzzle pieces you need right now, asynchronous components let your app load only the parts that are immediately necessary. Other parts can be loaded later, as needed. - Faster Start: Your app starts up faster because it doesn't have to load everything at once. It's like quickly starting with the border of a puzzle and filling in the rest later. - Save Resources: It uses your web resources (like internet data) more wisely, only grabbing what’s essential when it's essential. In short, asynchronous components make your app quicker to start and more efficient, improving the overall experience for your users. Example: ` Combining Async Components and Suspense Let's explore how combining asynchronous components with Vue's Suspense feature can enhance your application. When asynchronous components are used with Vue's Suspense, they form a powerful combination. The key point is that async components are "suspensable" by default. This means they can be easily integrated with Suspense to improve how your app handles loading and rendering components. When used together, you can do the following things: - Centralized Loading and Error Handling: With Suspense, you don't have to handle loading and error states individually for each async component. Instead, you can define a single loading indicator or error message within the Suspense component. This unified approach simplifies your code and ensures consistency across different parts of your app. - Flexible and Clean Code Structure: By combining async components with Suspense, your code becomes more organized and easier to maintain. An asynchronous component has the flexibility to operate independently of Suspense's oversight. By setting suspensible: false in its options, the component takes charge of its own loading behavior. This means that instead of relying on Suspense to manage when it appears, the component itself dictates its loading state and presentation. This option is particularly useful for components that have specific loading logic or visuals they need to maintain, separate from the broader Suspense-driven loading strategy in the application. In practice, this combo allows you to create a user interface that feels responsive and cohesive. Users see a well-timed loading indicator while the necessary components are being fetched, and if something goes wrong, a single, well-crafted error message is displayed. It's like ensuring that the entire puzzle is either revealed in its completed form or not at all rather than showing disjointed parts at different times. How it works When a component inside the boundary is waiting for something asynchronous, shows fallback content. This fallback content can be anything you choose, such as a loading spinner or a message indicating that data is being loaded. Example Usage Let’s use a simple example: In the visual example provided, imagine we have two Vue components: one showcasing a selected Pokémon, Eevee, and a carousel showcasing a variety of other Pokémon. Both components are designed to fetch data asynchronously. Without , while the data is being fetched, we would typically see two separate loading indicators: one for the Eevee Pokemon that is selected and another for the carousel. This can make the page look disjointed and be a less-than-ideal user experience. We could display a single, cohesive loading indicator by wrapping both components inside a boundary. This unified loading state would persist until all the data for both components—the single Pokémon display and the carousel—has been fetched and is ready to be rendered. Here's how you might structure the code for such a scenario: ` Here, is the component that's performing asynchronous operations. While loading, the text 'Loading...' is displayed to the user. Great! But what about when things don't go as planned and an error occurs? Currently, Vue's doesn't directly handle errors within its boundary. However, there's a neat workaround. You can use the onErrorCaptured() hook in the parent component of to catch and manage errors. Here's how it works: ` If we run this code, and let’s say that we had an error selecting our Pokemon, this is how it is going to display to the user: The error message is specifically tied to the component where the issue occurred, ensuring that it's the only part of your application that shows an error notification. Meanwhile, the rest of your components will continue to operate and display as intended, maintaining the overall user experience without widespread disruption. This targeted error handling keeps the application's functionality intact while indicating where the problem lies. Conclusion stands out as a formidable feature in Vue.js, transforming the management of asynchronous operations into a more streamlined and user-centric process. It not only elevates the user experience by ensuring smoother interactions during data loading phases but also enhances code maintainability and application performance. I hope you found this blog post enlightening and that it adds value to your Vue.js projects. As always, happy coding and continue to explore the vast possibilities Vue.js offers to make your applications more efficient and engaging!...

Nuxt DevTools v1.0: Redefining the Developer Experience Beyond Conventional Tools cover image

Nuxt DevTools v1.0: Redefining the Developer Experience Beyond Conventional Tools

In the ever-evolving world of web development, Nuxt.js has taken a monumental leap with the launch of Nuxt DevTools v1.0. More than just a set of tools, it's a game-changer—a faithful companion for developers. This groundbreaking release, available for all Nuxt projects and being defaulted from Nuxt v3.8 onwards, marks the beginning of a new era in developer tools. It's designed to simplify our development journey, offering unparalleled transparency, performance, and ease of use. Join me as we explore how Nuxt DevTools v1.0 is set to revolutionize our workflow, making development faster and more efficient than ever. What makes Nuxt DevTools so unique? Alright, let's start delving into the features that make this tool so amazing and unique. There are a lot, so buckle up! In-App DevTools The first thing that caught my attention is that breaking away from traditional browser extensions, Nuxt DevTools v1.0 is seamlessly integrated within your Nuxt app. This ensures universal compatibility across browsers and devices, offering a more stable and consistent development experience. This setup also means the tools are readily available in the app, making your work more efficient. It's a smart move from the usual browser extensions, making it a notable highlight. To use it you just need to press Shift + Option + D (macOS) or Shift + Alt + D (Windows): With simple keystrokes, the Nuxt DevTools v1.0 springs to life directly within your app, ready for action. This integration eliminates the need to toggle between windows or panels, keeping your workflow streamlined and focused. The tools are not only easily accessible but also intelligently designed to enhance your productivity. Pages, Components, and Componsables View The Pages, Components, and Composables View in Nuxt DevTools v1.0 are a clear roadmap for your app. They help you understand how your app is built by simply showing its structure. It's like having a map that makes sense of your app's layout, making the complex parts of your code easier to understand. This is really helpful for new developers learning about the app and experienced developers working on big projects. Pages View lists all your app's pages, making it easier to move around and see how your site is structured. What's impressive is the live update capability. As you explore the DevTools, you can see the changes happening in real-time, giving you instant feedback on your app's behavior. Components View is like a detailed map of all the parts (components) your app uses, showing you how they connect and depend on each other. This helps you keep everything organized, especially in big projects. You can inspect components, change layouts, see their references, and filter them. By showcasing all the auto-imported composables, Nuxt DevTools provides a clear overview of the composables in use, including their source files. This feature brings much-needed clarity to managing composables within large projects. You can also see short descriptions and documentation links in some of them. Together, these features give you a clear picture of your app's layout and workings, simplifying navigation and management. Modules and Static Assets Management This aspect of the DevTools revolutionizes module management. It displays all registered modules, documentation, and repository links, making it easy to discover and install new modules from the community! This makes managing and expanding your app's capabilities more straightforward than ever. On the other hand, handling static assets like images and videos becomes a breeze. The tool allows you to preview and integrate these assets effortlessly within the DevTools environment. These features significantly enhance the ease and efficiency of managing your app's dynamic and static elements. The Runtime Config and Payload Editor The Runtime Config and Payload Editor in Nuxt DevTools make working with your app's settings and data straightforward. The Runtime Config lets you play with different configuration settings in real time, like adjusting settings on the fly and seeing the effects immediately. This is great for fine-tuning your app without guesswork. The Payload Editor is all about managing the data your app handles, especially data passed from server to client. It's like having a direct view and control over the data your app uses and displays. This tool is handy for seeing how changes in data impact your app, making it easier to understand and debug data-related issues. Open Graph Preview The Open Graph Preview in Nuxt DevTools is a feature I find incredibly handy and a real time-saver. It lets you see how your app will appear when shared on social media platforms. This tool is crucial for SEO and social media presence, as it previews the Open Graph tags (like images and descriptions) used when your app is shared. No more deploying first to check if everything looks right – you can now tweak and get instant feedback within the DevTools. This feature not only streamlines the process of optimizing for social media but also ensures your app makes the best possible first impression online. Timeline The Timeline feature in Nuxt DevTools is another standout tool. It lets you track when and how each part of your app (like composables) is called. This is different from typical performance tools because it focuses on the high-level aspects of your app, like navigation events and composable calls, giving you a more practical view of your app's operation. It's particularly useful for understanding the sequence and impact of events and actions in your app, making it easier to spot issues and optimize performance. This timeline view brings a new level of clarity to monitoring your app's behavior in real-time. Production Build Analyzer The Production Build Analyzer feature in Nuxt DevTools v1.0 is like a health check for your app. It looks at your app's final build and shows you how to make it better and faster. Think of it as a doctor for your app, pointing out areas that need improvement and helping you optimize performance. API Playground The API Playground in Nuxt DevTools v1.0 is like a sandbox where you can play and experiment with your app's APIs. It's a space where you can easily test and try out different things without affecting your main app. This makes it a great tool for trying out new ideas or checking how changes might work. Some other cool features Another amazing aspect of Nuxt DevTools is the embedded full-featured VS Code. It's like having your favorite code editor inside the DevTools, with all its powerful features and extensions. It's incredibly convenient for making quick edits or tweaks to your code. Then there's the Component Inspector. Think of it as your code's detective tool. It lets you easily pinpoint and understand which parts of your code are behind specific elements on your page. This makes identifying and editing components a breeze. And remember customization! Nuxt DevTools lets you tweak its UI to suit your style. This means you can set up the tools just how you like them, making your development environment more comfortable and tailored to your preferences. Conclusion In summary, Nuxt DevTools v1.0 marks a revolutionary step in web development, offering a comprehensive suite of features that elevate the entire development process. Features like live updates, easy navigation, and a user-friendly interface enrich the development experience. Each tool within Nuxt DevTools v1.0 is thoughtfully designed to simplify and enhance how developers build and manage their applications. In essence, Nuxt DevTools v1.0 is more than just a toolkit; it's a transformative companion for developers seeking to build high-quality web applications more efficiently and effectively. It represents the future of web development tools, setting new standards in developer experience and productivity....

Nuxt.js for Complete Beginners cover image

Nuxt.js for Complete Beginners

The Vuejs Amsterdam online conference was held at the end of February in 2021. It brought together Vue.js enthusiasts and community members from around the world. Many interesting topics were presented and covered. The focus, of course, was on Vue.js 3. In addition, the creators of Nuxt.js had the opportunity to showcase it from the current standpoint in development, to their intentions for it down the track. They even demonstrated a pre-alpha version of Nuxt.js 3, that’s based on Vue.js 3. This article will start by briefly covering general Nuxt.js concepts, creating a new Nuxt.js app using the create-nuxt-app CLI, and finally, going through the different files and folders that Nuxt.js auto-generates for us. Let’s start! Nuxt.js Concepts Nuxt.js is a web development framework that builds on top of the Vue.js framework. It allows you to use your Vue.js skills to build a more confident, structured, SEO friendly website purely in Vue.js. Remember when you had to mess with the Vue.js SSR module and the Vue Meta module to build a SEO friendly website? On top of that, you had to install and use Vuex and Vue Router too! Well, Nuxt.js takes care of that! No more chaotic setup and scaffolding to start a new Vue.js app. With the help of the create-nuxt-app CLI, you can scaffold a Nuxt.js app in no time. What’s remarkable about Nuxt.js is its capacity to enforce convention over configuration. This means, you write less configuration files by sticking to a specific directory structure that makes Nuxt.js happy and saves you a ton of time! Apps supported by Nuxt.js Nuxt.js supports building a variety of web apps, including the following: Server-Side Rendering (SSR) SSR apps are also known as Universal Apps. The app gets rendered on the server-side before it is sent to the client-side, and is displayed in the browser. This is the best option when working on an SEO friendly website written in Vue.js. You can read the full documentation for the SSR apps here: SSR Apps Single Page Apps (SPA) This is what you’ve been doing so far with Vue.js. The app is compiled into a few JS and CSS files. When the user requests the app, the files are downloaded to the client-side, and the Vue.js engine takes over rendering and displaying the app. Static Site Generation (SSG) Nuxt.js can pre-render your app at build time. This means the entire app will be converted to simple static HTML files that can be hosted and served over a Content Delivery Network (CDN). The SSG option makes an app a legal JAMStack app. You can read the full documentation for the Static Site Generation here: Static Site Generation. File System Routing Nuxt.js automatically generates all the Vue.js Routes in your app based on the folder structure inside the *pages* folder. For example, consider having this folder structure: ` Nuxt.js automatically generates the following route configuration: ` You can read about *File System Routing* here: File System Routing. Data Fetching Inside a Nuxt.js app, you can still use the old techniques you kmow when developing Vue.js apps. However, we have a new player here! Server-side rendering. Nuxt.js provides a new set of data-hooks that you can implement so that Nuxt.js can prefetch data when generating the app at the server-side. Here are two data-hooks offered by Nuxt.js: fetch() hook This hook was introduced with Nuxt.js 2.12+ release. It can be used inside Vue.js components stored in the *pages* folder and *components* folder. asyncData() hook This hook has been around for a while now, and can be used only inside the Vue.js components stored in the *pages* folder. You can read more about *Data Fetching* hooks here: Data Fetching. Meta Tags and SEO Nuxt.js makes it so intuitive to add SEO support to your SSR app. You can add Metadata to your app at two different levels: - Globally using the *nuxt.config.js* file - Locally inside a Nuxt.js Page You can read about *Meta Tags and SEO* hooks here: Meta Tags and SEO. Create our first Nuxt.js app Let’s use the create-nuxt-app CLI, and create our first Nuxt.js app! Before you start, you want to make sure you have all the perquisites required before you can install and run the CLI. For this article, I am going to use npx. However, you can also use npm, or feel free to use yarn. Step 0 Start by running the following command: ` This command uses the create-nuxt-app tool, and specifies the name of the project- in this case *my-first-nuxt-app*. The CLI will ask you a few questions that are important to scaffold the new Nuxt.js app based on your own preferences and decisions. Here’s what to expect. Step 1 First, let’s confirm the project name as shown in the Figure 1. Give the app a name and hit Enter. _Figure 1: Specify project name_ Step 2 You’ve got to choose whether you want to develop your app with TypeScript or JavaScript. I will select *JavaScript* as shown in Figure 2. _Figure 2: Programming language_ Step 3 Next, you need to choose between Npm or Yarn. I will select *Npm* as shown in Figure 3. _Figure 3: Package manager_ Step 4 In this step, you’ve got to select the UI framework you are going to use in the app. I will select *Tailwind CSS* as shown in Figure 4. Even if you skip this step, you can add any UI framework you want later. _Figure 4: UI framework_ Step 5 Nuxt.js offers a set of modules that you can use right away in your apps. For this app, I will pick the *Axios* module. Figure 5 shows the selection. _Figure 5: Nuxt.js modules_ Step 6 The CLI makes it super intuitive to integrate linters in your app. I will pick up both *ESLint* and *Prettier*. Figure 6 shows the selection. _Figure 6: Linting tools_ Step 7 Now it’s time to select a testing framework. For the sake of this article, I will select *None*. Feel free to add any. Figure 7 shows the selection. _Figure 7: Testing framework_ Step 8 By default, Nuxt.js supports two rendering modes. SSR/SSG and SPA. I will pick *SSR/SSG* to take advantage of the Server-side rendering. Figure 8 shows the selection. _Figure 8: Rendering mode_ Step 9 The deployment target depends on our selection in Step 8. In this case, we have two options to select from. Server (using a Node.js server hosting) or Static (CDN/JAMStack hosting). I will select the *Server* deployment target as shown in Figure 9. _Figure 9: Deployment target_ Step 10 For the development tools, I will keep it simple and select the *jsconfig.json* option as shown in Figure 10. _Figure 10: Development tools_ Step 11 I won’t be using any continuous integration for this app. I will simply select *None* as shown in Figure 11. _Figure 11: Continuous integration_ Step 12 Finally, the CLI asks whether you want to use any version control system. A version control system is always recommended when doing any kind of development. I will select *Git* as shown in Figure 12. _Figure 12: Version control system_ These twelve questions are enough for the CLI to start scaffolding and generating your app based on your preferences. It takes a few seconds to have everything ready for you. If all goes well, you should see the following as in Figure 13. _Figure 13: create-nuxt-app new app instructions_ The CLI gives you instructions on how to run and build the app. Step 13 Let’s run the app by following the steps highlighted in Figure 13. Run the following commands: ` The CLI compiles both the client and server parts of the app and starts the Node.js server on port 3000 as shown in Figure 14. _Figure 14: App is running_ Step 14 Open a browser instance and navigate to the URL *http://localhost:3000/* and you should see the default Nuxt.js app rendering. Figure 15 shows the app running in a browser. _Figure 15: App rendering in a browser_ That’s all you need to get started on your first Nuxt.js app. Enjoy! Nuxt.js Directory Structure Let’s quickly go through the different folders the CLI generated for us. I will start by opening the new app inside Visual Studio Code. Figure 16 shows the app open inside the editor. _Figure 16: App folders and files_ Let’s go through each folder, and explain their roles briefly. .nuxt The *.nuxt* folder is (re-)generated by the CLI every time you run or build the app. It has all the automatically generated files that Nuxt.js uses to run your app. You can read more about the *.nuxt* folder here: .nuxt. assets The *assets* folder contains all of your uncompiled files such as Sass files, images, or font files. Nuxt.js makes use of Webpack to load all the files inside this folder. You can read more about the *assets* folder here: assets. components This folder holds all of your Vue.js components. Nuxt.js components are not different from any other Vue.js component. Read more about the *components* folder here: components. layouts This folder contains all of your layout components. These are Vue.js components with placeholders for content. At run time, the component and the layout it uses get merged together into a single component. Layouts in Nuxt.js allows you to define fixed UI sections in your app instead of having to repeat things over and over. You can read more about the *layouts* folder here: layouts. pages This folder also holds Vue.js components. It is unique because Nuxt.js converts this folder structure (components with sub-folders) into actual Vue.js Routes. You can read about the *pages* folder here: pages. plugins This folder contains global JavaScript functions that you want to run before Nuxt.js instantiates the root Vue.js app. These functions are called *Plugins*. They can take multiple forms. For instance, you create a Nuxt.js Plugin to load a Vue.js Plugin to your app. You can also install, and make use of a third-party Nuxt.js Plugin. You can read more about the *plugins* folder here: plugins. static The name of this folder says it all! This folder is directly mapped to the server root. You can place any file in this folder that you do not want Webpack to process. For example, you can place a favicon file, any CSS files, and many other such files. You can read more about the *static* folder here: static. store The *store* folder holds all the Vuex store files. Nuxt.js comes with the Vuex module installed, but keeps it disabled. In order to enable the Vue store in your Nuxt.js app, create an *index.js* file inside this folder, and Nuxt.js will automatically enable it for you. You can read more about *store* folder here: store. nuxt.config.js As I mentioned, Nuxt.js prefers convention over configuration. However, at some times you may need to tweak the configuration a little. Nuxt.js provides the *nuxt.config.js* file to allow for custom configuration settings on the app level. Read more about *nuxt.config* file here: nuxt.config. That’s just a quick overview of what folders are generated by the Nuxt.js CLI. There are more you can read up on. You can find all the information you need on Nuxt.js Documentation website. Conclusion In this article, you were introduced to Nuxt.js and took a few baby steps towards creating your first Nuxt.js app. In the coming articles, we will immerse ourselves in the Nuxt.js world, and explore this fresh and promising web development framework together. You can follow me on Twitter to see more of my work....

CSS Container Queries, what are they? cover image

CSS Container Queries, what are they?

CSS Container queries, what are they? Intro Media queries have always been crucial to building web applications. They help make our apps more accessible and easier to use and ensure we reach most of our audience. Media queries have been essential in frontend development to create unique user interfaces. But now, there’s something new: Container queries. In this blog post, we’ll explore what Container queries are, how they differ from media queries, and why they’re so amazing. So, let’s get started! Refresh on Media queries Media queries have been available in browsers for a long time, but they didn’t become popular until around 2010 when mobile devices started to take off. Media queries let us add specific styles based on the type of device, like screens or printers. This is especially helpful for creating modern, responsive apps. A simple use of Media queries would be changing, for example, a paragraph's font size when the screen width is less than a specific number. ` In this simple example, when the browser’s viewport width is less or equal to 400px, the font size changes to 8px. Notice how straightforward the syntax is: we start with the keyword @media, followed by the type of device it should apply to. In this case, we use screen so it doesn’t affect users who print the page—if you don’t add anything, then it falls back to the default, which is “all” including both print and screen. Then we specify a media feature, in this case, the width. Container queries Container queries are similar to Media queries. Their main function is to apply styles under certain conditions. The difference is that instead of listening to the viewport of the browser, it listens to a container size. Let’s see this example: In the above layout, we have a layout with a sidebar and three cards as the content. Using Media queries we could listen to the viewport width and change the layout depending on a specific width. Like so: ` That’s acceptable, but it requires us to constantly monitor the layout. For example, if we added another sidebar on the right (really weird, but let’s imagine that this is a typical case), our layout would become more condensed: We would need to change our media queries and adjust their range in this situation. Wouldn’t it be better to check the card container’s width and update its styles based on that? That way, we wouldn’t need to worry about if the layout changes, and that’s precisely what container queries are made for! First, to define the container we are going to listen to, we are going to add a new property to our styles: ` The .container class is the one in which our cards reside. By adding the property `container-type, ' we now define this class as a container we want to listen to. We said inline-size as the value to query based on the inline dimensions of the container because we just want to listen to the element's width. The value of container-type will depend on your use case. If you want to listen to both width and height, then size will be a better fit for you. You can also have normal as your container-type value, which means the element won’t act as a query container at all. This is handy if you need to revert to the default behavior. Next, to define our query, we use the new @container CSS at-rule: ` Notice that it is really similar to how we define our Media queries. Now, if we look at the same screen, we will see the following: This is very powerful because we can now style each component with its own rules without changing the rules based on the layout changes. The @container will affect all the defined containers in the scope; we might not want that. We can define the name of our container to specify that we only want to listen to that in specific: ` We can also have a shorthand to define our container and its name: ` Container query length units Container query lengths are similar to the viewport-percentage length units like vh or vw units, but instead of being relative to the viewport, they are to the dimensions of the query container. We have different units, each relative to different dimensions of the container: - cqw: 1% of a query container's width - cqh: 1% of a query container's height - cqi: 1% of a query container's inline size - cqb: 1% of a query container's block size - cqmin: The smaller value of either cqi or cqb - cqmax: The larger value of either cqi or cqb In our example, we could use them to define the font size of our cards: ` Using these units alone isn’t recommended because they’re percentage-based and can have a value we don’t want. Instead, it’s better to use a dynamic range. Using the max function, we can set 2 values and always pick the highest one. Conclusion Container queries bring a fresh and powerful approach to web design but are not meant to replace Media queries. I think their real power shines when used together. Media queries often require constant adjustments as your layout evolves. Container queries, however, let you style individual components based on their dimensions, making the designs more flexible and easier to manage. Adding a new component or rearranging elements won’t force us to rewrite our media queries. Instead, each component handles its styling, leading to cleaner and more organized code. Please note that, as of writing this blog post, they aren’t compatible with all browsers yet. Take a look at this table from caniuse.com: A good fallback strategy for this, when hitting an unsupported browser would be the use of the @support rule, which allows you to apply styles only if the browser supports the CSS feature. For example: ` Ensure your media queries are good enough to keep everything responsive and user-friendly when the condition is unmet. Thank you for reading! Enjoy the extra flexibility that container queries bring to your web designs. Check out a live demo to see it in action. Happy styling!...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co