How I Built an App with Vulcan.js in Four Days

As a seasoned full-stack developer, I‘ve used my share of web frameworks over the years – Rails, Django, MEAN, LAMP, you name it. I‘m always on the lookout for tools that can help me deliver high-quality software faster and more efficiently.

Recently, I decided to take the Vulcan.js framework for a spin. Vulcan.js is a relatively new entrant in the crowded JavaScript framework space, but it‘s quickly gaining traction among developers who value productivity and code quality. After building my first Vulcan.js app, I can see why.

In this post, I‘ll share my experience using Vulcan.js to build a non-trivial web application in just four days. I‘ll discuss what makes Vulcan.js unique, analyze the tradeoffs in its approach, and explore the trends in the JavaScript ecosystem that make it a compelling option for many projects.

The JavaScript Framework Landscape

Before we dive into the specifics of Vulcan.js, it‘s worth zooming out and looking at the state of web development in 2022. By any measure, JavaScript is eating the software world. According to the 2021 Stack Overflow Developer Survey, JavaScript is by far the most popular programming language, used by nearly 70% of all professional developers.

JavaScript leads the pack in programming language popularity

Within the JavaScript ecosystem, there‘s an overwhelming array of frameworks and libraries to choose from. The "Big Three" – React, Angular, and Vue – dominate mindshare and adoption.

React, Angular, and Vue are the leading JavaScript frameworks

However, a number of newer frameworks are starting to gain traction by offering innovative approaches to common problems. One of the most promising is Vulcan.js.

What is Vulcan.js?

Vulcan.js bills itself as "The full-stack React+GraphQL framework". But what does that actually mean?

At its core, Vulcan.js is a set of open-source npm packages, generator scripts, and conventions that help you build web and mobile apps with React, Node.js, and MongoDB. It‘s evolved from the Telescope framework (created by Sacha Greif of State of JS fame), and incorporates best practices and patterns from the Meteor framework.

Some of Vulcan.js‘s key features include:

  • Isomorphic JavaScript – The same code runs on the client and server
  • Convention over configuration – Sensible defaults allow you to focus on your app, not boilerplate
  • Extensible schemas – Easily add fields to existing data models
  • Code generators – Create new routes, components, and data models with simple CLI commands
  • Real-time updates – Changes in the database automatically sync with the UI
  • Server-side rendering – Initial page loads are fast and SEO-friendly

In essence, Vulcan.js aims to provide a complete framework for building real-world CRUD apps with modern JavaScript. It‘s not just a grab bag of tools, but a curated, end-to-end solution.

Vulcan.js vs. Other Frameworks

If you‘re an experienced JavaScript developer, you might be wondering how Vulcan.js compares to other popular frameworks and libraries. Let‘s look at a few common tasks and how Vulcan.js handles them.

Defining a Data Model

In Vulcan.js, you define your data models using standard JavaScript objects. Here‘s an example Movie schema:

// modules/movies/collection.js
const schema = {
  _id: {
    type: String,
    optional: true,
    canRead: [‘guests‘],
  },
  title: {
    type: String,
    optional: false,
    canRead: [‘guests‘],
    canCreate: [‘members‘],
    canUpdate: [‘members‘],
    searchable: true,
  },
  year: {
    type: Number,
    optional: true,
    canRead: [‘guests‘],
    canCreate: [‘members‘],
    canUpdate: [‘members‘],
    searchable: true,
  },
};

export const Movies = createCollection({
  collectionName: ‘Movies‘,
  typeName: ‘Movie‘,
  schema,
  resolvers: getDefaultResolvers(‘Movies‘),
  mutations: getDefaultMutations(‘Movies‘),
});

// modules/movies/fragments.js  
registerFragment(/* GraphQL */`
  fragment MovieFragment on Movie {
    _id
    title
    year
  }  
`);

This is notably simpler and more declarative than the equivalent code in other frameworks. For comparison, here‘s how you might define the same schema in Mongoose (a popular MongoDB ODM):

const movieSchema = new mongoose.Schema({
  title: { 
    type: String,
    required: true,
    index: true,  
  },
  year: {
    type: Number,
  },
});

const Movie = mongoose.model(‘Movie‘, movieSchema);

And in Rails ActiveRecord:

class Movie < ApplicationRecord
  validates :title, presence: true
end  

Vulcan.js schemas are more explicit about access control, support schema inheritance out of the box, and automatically generate GraphQL types and mutations. This declarative approach makes your code more readable and reduces opportunities for bugs.

Querying Data

In Vulcan.js, you query data using GraphQL. Vulcan.js automatically generates a CRUD API for your models based on your schema. For example, to query a list of movies:

// In a React component 
import { useMulti } from ‘meteor/vulcan:core‘;
import { Movies } from ‘../../modules/movies/collection‘;  

const MoviesIndex = () => {
  const { loading, results } = useMulti({
    collection: Movies,
    fragmentName: ‘MoviesIndex‘,
  });

  return (
    <div>
      {loading ? (
        <p>Loading...</p>  
      ) : (
        <ul>
          {results.map(movie => (
            <li key={movie._id}>{movie.title} ({movie.year})</li>
          ))}
        </ul>
      )}  
    </div>
  );
};

Compare this to querying data with Redux and a REST API:

// In a reducer
const initialState = {
  movies: [],
  isLoading: false,
  error: null,  
};

export default function moviesReducer(state = initialState, action) {
  switch (action.type) {
    case ‘FETCH_MOVIES_REQUEST‘:
      return { ...state, isLoading: true, error: null };
    case ‘FETCH_MOVIES_SUCCESS‘:
      return { ...state, isLoading: false, movies: action.payload };  
    case ‘FETCH_MOVIES_FAILURE‘:
      return { ...state, isLoading: false, error: action.error };
    default:
      return state;
  }
}

// In an action creator
export function fetchMovies() {
  return async dispatch => {
    dispatch({ type: ‘FETCH_MOVIES_REQUEST‘ });

    try {
      const res = await fetch(‘/api/movies‘);
      const payload = await res.json();

      dispatch({ type: ‘FETCH_MOVIES_SUCCESS‘, payload });
    } catch (error) {
      dispatch({ type: ‘FETCH_MOVIES_FAILURE‘, error });
    }
  };  
}

// In a component
import { useSelector, useDispatch } from ‘react-redux‘;
import { fetchMovies } from ‘./moviesSlice‘;

const MoviesIndex = () => {
  const dispatch = useDispatch();  
  const { movies, isLoading, error } = useSelector(state => state.movies);

  useEffect(() => {
    dispatch(fetchMovies());
  }, [dispatch]);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <ul>
      {movies.map(movie => (
        <li key={movie.id}>{movie.title} ({movie.year})</li>
      ))}
    </ul>
  );
};

The Vulcan.js version is significantly more concise. You don‘t have to define actions, reducers, or make manual HTTP requests. The useMulti hook handles all the plumbing for you. And because it uses GraphQL, it‘s trivial to specify exactly what fields you want to retrieve.

Mutations

Modifying data in Vulcan.js is similarly streamlined. To create a new movie:

// In a component
import { Components, registerComponent } from ‘meteor/vulcan:core‘;
import { Movies } from ‘../../modules/movies/collection‘;

const MoviesNew = () => (
  <Components.SmartForm 
    collection={Movies}
    mutationFragmentName="MovieFragment"
  />
);

registerComponent(‘MoviesNew‘, MoviesNew);  

Again, Vulcan.js generates the mutation and form UI automatically based on your schema. You can easily customize the form layout and fields if needed.

In Redux, you‘d need to define more actions and reducers:

// In a reducer  
case ‘CREATE_MOVIE_REQUEST‘:
  return { ...state, isLoading: true, error: null };  
case ‘CREATE_MOVIE_SUCCESS‘:
  return { 
    ...state, 
    isLoading: false,
    movies: [...state.movies, action.payload],
  };
case ‘CREATE_MOVIE_FAILURE‘:  
  return { ...state, isLoading: false, error: action.error };

// In an action creator
export function createMovie(movie) {
  return async dispatch => {
    dispatch({ type: ‘CREATE_MOVIE_REQUEST‘ });

    try {
      const res = await fetch(‘/api/movies‘, {
        method: ‘POST‘,
        headers: { ‘Content-Type‘: ‘application/json‘ },  
        body: JSON.stringify(movie),
      });

      const payload = await res.json();

      dispatch({ type: ‘CREATE_MOVIE_SUCCESS‘, payload });
    } catch (error) {
      dispatch({ type: ‘CREATE_MOVIE_FAILURE‘, error });  
    }
  };
}  

And you‘d still need to set up your server-side route and controller to handle the actual database insertion.

Clearly, Vulcan.js can dramatically reduce the amount of boilerplate required for common operations. But this power comes with tradeoffs.

The Opinionated Approach

One of the most striking things about Vulcan.js is how opinionated it is. The framework makes a lot of decisions for you – from the database (MongoDB) to the API protocol (GraphQL) to the UI library (React). For some developers, this is a turn-off. They prefer the flexibility to mix and match tools to fit their needs.

However, there are significant benefits to Vulcan.js‘s opinionated approach:

  • Less bikeshedding – When there‘s a canonical "Vulcan.js way" to do something, you spend less time debating alternatives.
  • Faster onboarding – Developers can be productive quickly because they have fewer concepts to learn.
  • Easier code sharing – When everyone is using the same patterns, it‘s easier to reuse code between projects.
  • Better performance – The Vulcan.js core team can optimize the framework for the specific use cases and libraries it targets.

That said, Vulcan.js is far from a walled garden. You can still use any JavaScript library in your project. And the modular architecture makes it possible to swap out pieces if needed.

Productivity Boost

Personally, the biggest benefit of Vulcan.js is how productive it makes me. As an experienced developer, I‘ve gotten used to spending hours on tedious plumbing for each new project – setting up build tooling, creating CRUD boilerplate, configuring user authentication. With Vulcan.js, all of that is taken care of. I can focus on the features that deliver real value to users.

In the four days I spent building my app, I estimate that using Vulcan.js saved me from writing around 2,000 lines of code. On top of that, I probably saved another 6-8 hours of dev time that I would have spent configuring libraries, debugging state management, and so on. That‘s a huge productivity boost!

But crucially, I didn‘t have to sacrifice code quality or maintainability to achieve this velocity. Because Vulcan.js is built on battle-tested, industry-standard libraries and patterns, I can be confident that my codebase will be easy to extend and refactor as it grows.

Tradeoffs and Limitations

Of course, no framework is perfect for every use case. Vulcan.js is no exception. Its reliance on MongoDB makes it poorly suited for applications that require complex relational queries. Its tight coupling between the client and server can be limiting if you need to integrate with third-party backends. And any time you‘re using a relatively young framework, you risk running into edge cases that haven‘t been fully tested.

For me, the productivity benefits outweigh these tradeoffs for many projects. But your mileage may vary. As with any tool, it‘s important to evaluate it in the context of your specific needs and constraints.

Conclusion

After years of bouncing between JavaScript frameworks, I‘ve found my new go-to tool for building CRUD apps in record time. Vulcan.js‘s opinionated approach, declarative APIs, and built-in best practices allow me to deliver high-quality software faster than ever before.

It may not be the right fit for every project. But if you‘re an experienced JavaScript developer looking to 10x your productivity, I highly recommend giving Vulcan.js a try. It‘s already transformed how I build apps, and I predict we‘ll be seeing a lot more Vulcan-powered projects in production as the framework matures.

To learn more about Vulcan.js, check out the following resources:

I also recommend reaching out to the friendly and knowledgeable folks in the Vulcan.js Slack workspace.

Happy coding!

Similar Posts