I can't setup a react apollo mongodb app => resolvers with mongo / mongoose / NO METEOR - mongodb

I'm currently learning Apollo, I'm a front end developer ( angular 1.5 over half a year for the background )
I have a very little experience with mongodb, and also with apollo ( trhough a meteor app )
I've read several times the Apollo doc, parsed Google with the keywords apollo, react, mongodb, resolvers, with various combinations ... ( no results )
I need to build a little React app (POC) which handle query, mutations, and subscriptions which would persist data with mongodb ( or another DB, I indicate this one since this is only one I've worked with so far ).
I cannot use Meteor.
My problem is that I can't find proper examples for graphql resolver working with mongo for all cases I need ( query, sub, mutate).
Would you have some boilerplate to provide , to help me to understand the mecanism ?
Thanks.

If you do not want to go through some tutorials. You could look at the githunt project. It is a apollo-graphql implementation with queries, mutations and subscriptions. It does not use a mongoDB, but this should be an easy change.
server: https://github.com/apollographql/GitHunt-API/tree/master/api
client: https://github.com/apollographql/GitHunt-React
Cheers

I think graphqly is the answer for you.
import graphly from "graphqly";
const gBuilder = graphly.createBuilder();
// define types, inputs ... (in any order)
gBuilder.type("Products").implements("List").def(`
products: [Product]!
`);
gBuilder.type("Product").def(`
id: ID!
name: String!
link: String
price: Int
`);
// we're too lazy to define a separate input, so we can `extend` other structure
gBuilder.input("ProductInput").ext("Product");
gBuilder.enum("ProductOrder").def(`
PRICE_DESCENDING
PRICE_ASCENDING
NEWEST
`);
// define interface `List`
gBuilder.iface("List").def(`
total: Int!,
offset: Int!,
limit: Int!,
# number of items actually in this window
size: Int!
`);
gBuilder.query(`
products(limit: Int = 20, offset: Int = 0, filter: ProductFilter): Products
`)
.resolve((root, args, context) => {
// your resolver here
// `this` is binded to the current Query instance
});

So, this is a good start for server only part with mongo / mongoose :
https://github.com/khadorkin/apollo-server-mongoose
I've enhanced this, I'll provide a link to the github repo in a few days.
Here is the repo.
Still work to be done, specially about subscriptions, but it might be a good start
Link to Github repo

Related

Mongoose ('findOneAndUpdate') Middleware: Need access to original document

I am attempting to use pre('findOneAndUpdate') to update the icon attribute of the Meeting document. The update is based on the pre-existing value of the yearlymeeting attribute (see below).
Because pre and post save() hooks are not executed on update(), I seem to be unable to access the original document at all. Yet this is critical for the operation I'm trying to perform. Is there any way around this?
For example, I am able to accomplish my purpose on pre('save'), like so:
meetingSchema.pre('save', function(next) {
const yearlymeetingSlug = this.yearlymeeting[0].toLowerCase().replace(/[^A-z0-9]/g, '');
this.icon = `${yearlymeetingSlug}.png`
next();
});
What I would like to be able to do is something like this:
meetingSchema.pre('findOneAndUpdate', function(next) {
const yearlymeetingSlug = originalDocument.yearlymeeting[0].toLowerCase().replace(/[^A-z0-9]/g, '');
this.icon = `${yearlymeetingSlug}.png`
next();
});
I understand that this in pre(findOneAndUpdate) refers to the query, rather than the stored document itself. Is there any way to access the document, so that I can update icon based on the stored value of yearlymeeting?
tl;dr
Not possible via middleware. Query for the doc first, and then separately update a specific version of the doc to prevent race conditions.
Can't do it the way you're trying according to this issue on the Mongoose Github (from the main dev):
By design - the document being updated might not even be in the server's memory. In order to do that, mongoose would have to do a findOne() to load the document before doing the update(), which is not acceptable.
The design is to enable you to manipulate the query object by adding or removing filters, update params, options, etc. For instance, automatically calling .populate() with find() and findOne(), setting the multi: true option by default on certain models, access control, and other possibilities.
findOneAndUpdate() is a bit of a misnomer, it uses the underlying mongodb findAndModify command, it's not the same as findOne() + update(). As a separate operation, it should have its own middleware.
Following this, there are no other suggestions in the issue thread to access the original document inside of the middleware itself.
What I've seen done (and what I've had to do many times myself), is simply have to query for the document before updating it (which, of course, could lead to a race condition depending on who is updating the doc, and when, but you can fix that by also querying for a specific version of the document -- a sort of "optimistic locking"):
let meeting = yield Meeting.findOne({}).exec()
let update = {}
// ... some conditional logic to figure out which icon to set
update.icon = // whatever
yield Meeting.update({ _id: meeting._id, version: meeting.version }, update)
This is of course assuming you have a "version" field in your schema. This sort of locking will prevent you from updating an old version of the doc. If you're gonna use this kind of versioning, you'll also probably want to add some middleware that updates the version of a doc any time the doc is updated/saved.
You can also use a more naïve implementation, where you don't use locking, which may be fine in your specific business case, as long as you're aware of the possibility of a race condition, and the risks.
This may not be the best solution, but I did find a way to make it work. I used the controller rather than schema pre hooks. Here's what my update controller looks like now:
exports.updateMeeting = async (req, res) => {
const _id = req.params.id
let meeting = await Meeting.findOneAndUpdate({ _id }, req.body, {
new: true,
runValidators: true
});
/* New Code: */
const yearlymeetingSlug = meeting.yearlymeeting[0].toLowerCase().replace(/[^A-z0-9]/g, '');
meeting.icon = `${yearlymeetingSlug}.png`;
meeting.save();
req.flash('success', 'meeting successfully updated!');
res.redirect(`/meetings/${meeting.slug}`);
};
I welcome your feedback on any problems you see with this solution.

Meteor.subscribe on server side

I want to create a backend service which monitors a mongodb collection for new entries. As those are being created, I wish to run processing and update them.
I thought doing so with a Meteor service/app would be a wise idea because Meteor uses 'oplog tailing' which seems ideal for this purpose (I'd rather avoid polling if possible).
As such, I figured creating a minimal server-side-only app should solve it.
So basically, I need something along these lines:
if (Meteor.isServer) {
MyCollection = new Mongo.Collection('myCollection');
Meteor.publish('myCollectionPub', function () {
return MyCollection.find({ some: criteria... });
}
// is there such a thing?
Meteor.serverSideSubscribe('MyCollectionPub',
function (newDocs) {
// process/update newDocs
});
}
According to the Meteor docs, I cannot use Meteor.subscribe() on the server (and indeed it crashes if I try).
Question is:
Are there ways of 'subscribing' to collection updates on the server?
The PeerLibrary server-autorun package (along with it's dependant, reactive-mongo) will provide you with easy server-side observation of collections.
An alternative to #tarmes suggestion is the collection-hooks package, however as pointed out by David Weldon, it will only trigger in instance it is run in:
https://github.com/matb33/meteor-collection-hooks
MyCollection.after.insert(function (userId, doc) {
// ...
});
If you need it to run even when another instance makes a change in the mongo database, you can observe a cursor that is returned from your collection:
MyCollection.find({created_at : {$gt: some_current_time}}).observe({
added: function(item) {
// Alert code
}
});

How to handle changes to db in meteor/mongo?

I'm a couple hours new to Meteor and Mongo, coming from a Rails background and trying to understand how migrations work - or don't maybe?
I have a server/bootstrap.js file that I use to seed some data:
// if the database is empty on server start, create some sample data.
Meteor.startup(function () {
if (Users.find().count() === 0) {
var userData = [
{ name: 'Cool guy' },
{ name: 'Other dude' }
];
for (var i = 0; userData.length; i++) {
var userId = Users.insert({
name: userData[i].name
});
}
}
});
It seems like every time I want to change the database, say to add a new field, I have to run meteor reset to get it to pick up the changes.
But what happens if I create records or other data through the UI that I want to keep? In Rails, working with MySQL or PostgreSQL, I'd create a migration to create new fields without blowing away the entire database.
How does this work with Meteor and Mongo? Also thinking of the case of rolling out new changes from development to production. Thanks!
-- Update: 2013/09/24 --
Apparently, the schema-less nature of Mongo reduces or eliminates the need for migrations. In my case, modifying userData to add new fields won't work after it runs initially because of the Users count check - which is why I kept running meteor reset. I'll need to rethink my approach here and study up.
That said, there are projects out there that use migrations, like Telescope: https://github.com/SachaG/Telescope/blob/master/server/migrations.js
I also found the tutorial at http://try.mongodb.org/ useful.
First of all, your code is perfectly valid. And you know that.
mrt reset gives you a 'fresh' - empty database (as mentionned already).
If you want to reset a particular collection, you can do it so :
MyCollection.remove({});
But you have to understand the nature of NoSQL : there are no constraints on the data. It could be called NoREL (as in not a relational database, source : Wikipedia ).
MongoDB is also schema-less.
This means that you can use any field you want in your data. This is up to you (the programmer) to enforce specific constraints if you want some. In other words, there is no logic on the mongo side. It should accept any data you throw at it, just like Hubert OG demonstrated. Your code snippet could be :
// if the database is empty on server start, create some sample data.
Meteor.startup(function () {
if (Users.find().count() === 0) {
var userData = [
{ name: 'Cool guy' },
{ name: 'Other dude' },
{ nickname: 'Yet another dude' } // this line shows that mongo takes what you throw him
];
for (var i = 0; userData.length; i++) {
var userId = Users.insert({
name: userData[i].name
});
}
}
});
Source : http://www.mongodb.com/nosql
There is no need for migration there. You only have to add the logic in your application code.
Note : To import/export a database, you can have a look there : mongo import/export doc, and maybe at the db.copyDatabase(origin, destination, hostname) function.
There are no migrations in Mongo — there is no scheme! If you want to add a new field that was not there before, just do it and it will work. You can even have completely different documents in the same collection!
Items.insert({name: "keyboard", type: "input", interface: "usb"});
Items.insert({cherries: true, count: 5, unit: "buckets", taste: "awesome"});
This will just work. One of main reasons to use NoSQL (and advantages of Meteor over Rails) is that you don't have migrations to worry about.
Using mrt reset to change db model is a terrible idea. What it actually does is complete reset of db — it removes all of your data! While it's sometimes usefull in development, I bet it's not what you want in this case.

Full-text search with Meteor.js and MongoDB

I am experimenting with Meteor.js and looking for a full-text search engine that can run on Meteor/MongoDB server. It seems that Meteor has not developed this feature.
I wonder if there is an existing solution for adding full-text search to Meteor app.
The only thing I can find is MongoLantern, which has node.js API. How should I go about adding MongoLantern to my Meteor app?
MongoDB 2.4 will have a full text search in it. Guide can be found here.
If you are prepared to run the development releases you can download MongoDB 2.3 now - it contains text search.
Update: MongoDB 2.4 has been released. Text search is described as Beta.
The guide for text search is here and the mongod must be run like this
mongod --setParameter textSearchEnabled=true
Take a look at http://lunrjs.com/. That might also help in getting the near-instant performance of real Meteor app.
You might want to have a look at:
https://github.com/Crenshinibon/spomet
It's a Meteor native package to provide full-text search. It has an easy to include search-box with autocompletion.
You can read a tutorial about an extended example application here:
http://shiggyenterprises.wordpress.com/2013/09/28/developing-a-full-text-search-enabled-meteor-app/
There is also another way to implement a solution with Meteor. It's Search Source.
It's a kind of typeahead but without the UI part. It exposes a reactive datasource where you can use it to build the UI with Blaze as you need.
Here's a demo app: https://instant-search-demo.meteor.com/
Above app is an instant search app to search Meteor packages. How it can build with search source is documented in this article
In brief this is how search source works:
In client, create a source
var options = {
keepHistory: 1000 * 60 * 5,
localSearch: true
};
var fields = ['packageName', 'description'];
PackageSearch = new SearchSource('packages', fields, options);
Then in the server define the search source
SearchSource.defineSource('packages', function(searchText, options) {
var options = {sort: {isoScore: -1}, limit: 20};
if(searchText) {
var regExp = buildRegExp(searchText);
var selector = {packageName: regExp, description: regExp};
return Packages.find(selector, options).fetch();
} else {
return Packages.find({}, options).fetch();
}
});
function buildRegExp(searchText) {
// this is dumb implementation
var parts = searchText.trim().split(' ');
return new RegExp("(" + parts.join('|') + ")", "ig");
}
Now get the data source and render it
Template.searchResult.helpers({
getPackages: function() {
return PackageSearch.getData({
transform: function(matchText, regExp) {
return matchText.replace(regExp, "<b>$&</b>")
},
sort: {isoScore: -1}
});
}
});
Finally do the search
PackageSearch.search("the text to search");
You can learn more about how each of the above works with from the documentation.

when updating item with changed embedded docs in Mongoose, these embedded docs don't update

I'm doing the following using Mongoose:
that.model.update({_id: dao._id}, dao, { upsert: true }, cb);
Where dao is a mongoose representation containing (among other things) a couple of embedded documents. As a test I've deleted a couple of the embedded docs from the array before calling the update-method above.
The result is that the change to the array of embedded docs IS NOT persisted.
Anything I'm overlooking?
Hard to be certain w/o seeing more code, but if dao is a Mongoose model instance, you should be calling dao.save(cb); instead.
I solved the problem by doing something similar as proposed in the following issue: https://github.com/LearnBoost/mongoose/issues/571
For completeness some background which led to the problem.
I'm using DDD repositories which are populated on app-start. Under the hood this fetches Mongoose-objects (which are treate as DAOs in my situation) and are translated to domainobjects, which are cached in the repository. I need this separation between domainobjects and mongoose-objects, don't ask.
This means that getById, getAll and all other public interfaces of the repo work with domainobjects and not with mongoose-objects.
When doing things like add or update on the repo this internally only updates the in-mem cache (which, again, only uses domainobjects instead of mongoose-objects)
Only when doing commit on the repo does the possibly changed collection of domainobjects get persisted. This is done by creating NEW Mongoose-objects instead of fetching Existing mongoose-objects and updating those.
This is why I can't use dao.save() since, when I'm saving a different (just created) mongoose-object while a mongoose-object with the same id may possibly already exist in Mongo, it throws a duplicate id error.
Some relevant snippet from by code illustrating the solution:
var dao = that.createDAO(domainobject);
//https://github.com/LearnBoost/mongoose/issues/571
// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = dao.toObject();
// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;
that.model.update({_id: dao._id}, upsertData, { upsert: true }, cb);