Easy CRUD setup with Meteor in only 4 steps from scratch
Tags: meteor, javascript, programming, beginners
Published: 2022-06-30 19:10:00

Meteor has probably the fastest and easiest way from installation to running a CRUD app out there: install, create project, tweak the boilerplate ui a bit, done! 😲

This is, because Meteor's devmode contains a package `insecure` that allows DB writes from the client, making prototyping new ideas super fast! 🔥

However, you will have to implement a real CRUD system, if things get more serious than just playing with the examples. Meteor will also provide a fast and reliable way to achieve this goal. Let me guide you through it. All you need to start is to open your console and follow the instructions. 🫡

<hr>
## Recap - what is CRUD?

As you might already know, CRUD stands for **C**reate - **R**ead - **U**pdate - **D**elete. They are the basic types of operations on a storage system.

Meteor uses MongoDB as storage system, which groups documents in so called [collections](https://guide.meteor.com/collections.html). CRUD operations are implement in Mongo via `insert` (create),  `find` (read), `update` (update) and `remove` (delete).

Implementing a CRUD system can be a good starting point for a new app idea but is also a common part many more complex setups. 

Now let's get started from scratch.


<hr>
## 1. Install Meteor and create a new project

Installing the latest Meteor version [is just a single line away](https://www.meteor.com/developers/install/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors): 

> For Windows, Linux and OS X, you can run the following command (Node.js 14.x is required):

```
npm install -g meteor
```

> An alternative for Linux and OS X, is to install Meteor by using curl:

```bash
curl https://install.meteor.com/ | sh
```

Once you have installed Meteor you can create a new project via

```bash
meteor create crud-app
cd crud-app
meteor
```

You can open your browser and navigate to `localhost:3000` to confirm it's all running properly. That's it for the installation part.


<hr>
## 2. Add CRUD functionality to the UI

The default `meteor create <projectname>` uses React as frontend but you can also chooser a different frontend, like Vue, Svelte, Blaze or others. Run `meteor create --help` to get a list of all available options.

With the React frontend you will also get a minimal app skeleton. Let's enhance the file `imports/ui/Info.jsx` a bit, to support all CRUD operations:

```jsx
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { LinksCollection } from '../api/links';

export const Info = () => {
  const [formTarget, setFormTarget] = useState(null);
  const links = useTracker(() => {
    return LinksCollection.find().fetch();
  });

  const renderLinkForm = () => {
    return formTarget
      ? (<LinkForm onSubmitted={() => setFormTarget(null)} doc={formTarget.doc} type={formTarget.type} />)
      : null
  };

  return (
    <div>
      <h2>Learn Meteor!</h2>
      <ul>{links.map(
        link => <li key={link._id}>
          <a href={link.url} target="_blank">{link.title}</a>
          <button onClick={() => setFormTarget({ type: 'update', doc: link })}>Update</button>
          <button onClick={() => LinksCollection.remove({ _id: link._id })}>Delete</button>
        </li>
      )}</ul>
      {renderLinkForm()}
      <button onClick={() => setFormTarget({ type: 'insert' })}>Create new</button>
    </div>
  );
};

const LinkForm = ({  type, doc, onSubmitted  }) => {
  const [title, setTitle] = useState(doc?.title ?? '');
  const [url, setUrl] = useState(doc?.url ?? '');

  const onSubmit = () => {
    let result;
    if (type === 'insert') {
      result = LinksCollection.insert({ title, url });
    }
    if (type === 'update') {
      result = LinksCollection.update(doc._id, { $set: { title, url } });
    }
    onSubmitted(result);
  };

  return (
    <form onSubmit={onSubmit}>
      <label>
        <span>Title</span>
        <input type="text" value={title} onChange={e => setTitle(e.target.value)} />
      </label>
      <label>
        <span>URL</span>
        <input type="text" value={url} onChange={e => setUrl(e.target.value)} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  )
}
```

Meteor will automatically reload the app in your browser. It should look like this now:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vz75l67knnmqfkrpwq5r.png)

You can now play around with the links and see all the changes in your Mongo Collection **on the server**. To verify this is working, open a new terminal in your OS and enter the project folder, then enter the following commands:

```bash
$ meteor mongo
# wait for the Mongo shell to start
meteor:PRIMARY> db.links.find()
{ "_id" : "F8BfbeWEbnuRNds65", "title" : "dev.to", "url" : "https://dev.to" }

```

It should list the updated collection, reflecting all your changes. That's it. A complete CRUD system. :tada:

Okay, not so fast. This is insecure as hell. 🔥 You can now close the Mongo shell and enter instead the following command:

```bash
$ meteor remove insecure
```

Now try again to edit your collection. It will not work and the browser console will display an error `Access denied [403]` each time you try to operate on the collection.

This is, because now without the `insecure` package being present you will actually have to create endpoints on the server that your client has to call. In Meteor these are called Meteor Methods.

The good thing: it's just as easy as everything has been up to this point.

<hr>
## 3. Implement the CRUD Methods on the server

Meteor Methods are endpoints that execute server-side code.
If you are new to Meteor you may later want to [read more about Methods in the official guide](https://guide.meteor.com/methods.html).

Until that, let's create the Methods in our server startup file `server/main.js`:

```js
import { Meteor } from 'meteor/meteor';
import { LinksCollection } from '/imports/api/links';

// ... keep the original code, 
// I just skip it here for better readability

Meteor.methods({
  'links.create': function ({ title, url }) {
    return LinksCollection.insert({ title, url })
  },
  'links.update': function ({ _id, title, url }) {
    const $set = {}
    if (title) { $set.title = title }
    if (url) { $set.url = url }
    return LinksCollection.update({ _id }, { $set })
  },
  'links.delete': function ({ _id }) {
    return LinksCollection.remove({ _id })
  }
})
```

<hr>
## 4. Make your client call the Methods

In the final step we only need to add two things to our client side code: 1. replace the insert/update/delete calls on `LinksCollection` with `Meteor.call` and 2. provide a simple error message, in case something went wrong on the server.

Here is the modified `Info.jsx` file:

```js
import { Meteor } from 'meteor/meteor';
import React, { useState } from 'react';
import { useTracker } from 'meteor/react-meteor-data';
import { LinksCollection } from '../api/links';

export const Info = () => {
  const [formTarget, setFormTarget] = useState(null);
  const [error, setError] = useState(null);
  const links = useTracker(() => {
    return LinksCollection.find().fetch();
  });

  const onSubmitted = () => {
    setFormTarget(null);
    setError(null);
  };
  const onError = (err) => setError(err);

  const renderLinkForm = () => {
    return formTarget
      ? (<LinkForm onSubmitted={onSubmitted} onError={onError} doc={formTarget.doc} type={formTarget.type} />)
      : null
  };

  const renderError = () => {
    return error
      ? (<div>{error.message}</div>)
      : null
  }
  const remove = _id => {
    Meteor.call('links.delete', { _id }, (err) => setError(err || null))
  }

  return (
    <div>
      <h2>Learn Meteor!</h2>
      <ul>{links.map(
        link => <li key={link._id}>
          <a href={link.url} target="_blank">{link.title}</a>
          <button onClick={() => setFormTarget({ type: 'update', doc: link })}>Update</button>
          <button onClick={() => remove(link._id)}>Delete</button>
        </li>
      )}</ul>
      {renderLinkForm()}
      {renderError()}
      <button onClick={() => setFormTarget({ type: 'insert' })}>Create new</button>
    </div>
  );
};

const LinkForm = ({  type, doc, onSubmitted, onError  }) => {
  const [title, setTitle] = useState(doc?.title ?? '');
  const [url, setUrl] = useState(doc?.url ?? '');

  const onSubmit = e => {
    e.preventDefault()
    if (type === 'insert') {
      Meteor.call('links.create', { title, url }, (err, res) => {
        if (err) { return onError(err); }
        onSubmitted(res);
      })
    }
    if (type === 'update') {
      Meteor.call('links.update', { _id: doc._id, title, url }, (err, res) => {
        if (err) { return onError(err); }
        onSubmitted(res);
      })
    }
  };

  return (
    <form onSubmit={onSubmit}>
      <label>
        <span>Title</span>
        <input type="text" value={title} onChange={e => setTitle(e.target.value)} />
      </label>
      <label>
        <span>URL</span>
        <input type="text" value={url} onChange={e => setUrl(e.target.value)} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  )
}
```

This is our first basic CRUD setup. :tada:

<hr>
## What comes next?

At this point, there are still many things uncovered:
- permissions check (authentication)
- validate input
- log errors on the server
- implement the CRUD methods in a generic way and reuse them on any collection

Next week I will publish the follow-up article, where all these topics will be covered. At the end you will have a running CRUD system and become ready for some serious action.
We will continue from exactly this point.

If you became interested in Meteor and want to dive-in quickly  I suggest you to visit the [Meteor University course "Meteor 101: The Fundamentals"](https://university.meteor.com/course/meteor-101/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors)! It's free and full of information to help you bring your ideas to deployment fast and reliable.

Furthermore, you can connect with the Meteor community via various channels:

- Website: https://www.meteor.com/
- GitHub: https://github.com/meteor/meteor
- Ambassador page: https://social.meteor.com/ambassador 
- Hosting: https://www.meteor.com/cloud 
- Twitter: https://twitter.com/meteorjs 
- Facebook: https://www.facebook.com/meteorjs 
- Instagram: https://www.instagram.com/meteor.js 
- YouTube: https://www.youtube.com/user/MeteorVideos 
- LinkedIn: https://www.linkedin.com/company/meteor-software 
- Slack: https://social.meteor.com/Slack 
- Podcast: https://podcast.meteor.com/ 
- Medium (Blog): https://blog.meteor.com/ 
- Forums: https://forums.meteor.com/ 

## Disclaimer

I am not directly financially affiliated with Meteor Software or any former company behind Meteor. However, I am now an elected member of the [Meteor Ambassador Program](https://ambassador.meteor.com/).

<hr>
I regularly publish articles here on dev.to about **Meteor** and **JavaScript**. If you like what you are reading and want to support me, you can [send me a tip via PayPal](https://paypal.me/kuesterjan).

You can also find (and contact) me on [GitHub](https://github.com/jankapunkt/), [Twitter](https://twitter.com/kuester_jan) and [LinkedIn](https://www.linkedin.com/in/jan-kuester/).

Keep up with the latest development on Meteor by visiting [their blog](https://blog.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors) and if you are the same into Meteor like I am and want to show it to the world, you should check out the [Meteor merch store](https://shop.meteor.com/?utm_source=jkuester&utm_medium=online&utm_campaign=Q2-2022-Ambassadors).