Easy CRUD setup with Meteor in only 4 steps from scratch
Jan Küster
Posted on June 30, 2022
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. 🫡
Recap - what is CRUD?
As you might already know, CRUD stands for Create - Read - Update - Delete. They are the basic types of operations on a storage system.
Meteor uses MongoDB as storage system, which groups documents in so called collections. 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.
1. Install Meteor and create a new project
Installing the latest Meteor version is just a single line away:
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:
curl https://install.meteor.com/ | sh
Once you have installed Meteor you can create a new project via
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.
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:
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:
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:
$ 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. 🎉
Okay, not so fast. This is insecure as hell. 🔥 You can now close the Mongo shell and enter instead the following command:
$ 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.
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.
Until that, let's create the Methods in our server startup file server/main.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 })
}
})
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:
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. 🎉
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"! 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.
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.
You can also find (and contact) me on GitHub, Twitter and LinkedIn.
Keep up with the latest development on Meteor by visiting their blog 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.
Posted on June 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.