Using Slash GraphQL to Create InstaMeme—A Meme Sharing App
Walk through the process of creating a Slash GraphQL backend service to meet the needs of a fictional client, called InstaMeme.
Join the DZone community and get the full member experience.
Join For FreeMemes are one of my favorite things about the internet. They are a virtual postcard capturing moments of internet history ... from a comical perspective.
For those who are not quite sure what a "meme" is, Ben Stegner uses the following definition:
A piece of media, often humorous, that spreads rapidly through the internet.
In fact, in this context, memes are actually Internet Memes as defined by Wikipedia:
A type of idea, behavior, or style (meme) that is spread via the internet, often through social media platforms and especially for humorous purposes.
The original meme design included some form of a photo, with text above and below the image using the Impact font face. Here is a classic example from the original Willy Wonka & the Chocolate Factory movie:
For the last month, the following photo taken of Bernie Sanders has been the subject of several memes:
In this example, Bernie is placed on Mars shortly after the Mars 2020 rover landed:
In fact, one of my teammates (named Kurtis) actually shaved his head in a male-pattern baldness manner just to create the following presence during a team meeting:
My biggest challenge, from working long hours each week and fathering a toddler, is keeping up with the most-recent memes. So I began to wonder, "what if there was a social media app focused on providing the most popular or trending memes?"
Since I am continuing to learn more about GraphQL, I thought I would prototype an Instagram-like clone for sharing memes.
Creating InstaMeme
For the most part, I really enjoy what social media applications provide to society. They have allowed me to reconnect/stay connected with friends and colleagues, sharing updates for all to see. This is far better than using an alternative, like email.
While I know that Instagram, Twitter, and Facebook allow sharing photos, I feel like I am still missing out on so many funny memes. I believe the underlying design of the popular social media applications is not truly focused on making sure I keep current on what everyone else thinks is worth sharing.
For that reason, I wanted to prototype something I will refer to as "InstaMeme". It's similar to Instagram but focused on keeping everyone up to date with the latest/trending memes. For the record, I am not really planning to build this application, but rather using it as an idea to prototype some data.
When adding memes to InstaMeme, the end user must select from a list of categories. Below is a list of the categories I thought would be valid:
animals
pop culture
movies
music
sports
Consider this basic application mockup for the InstaMeme user interface:
When users load the application, they will be presented with a list of memes that they have not previously ranked. The application will show the highest-ranked memes first, based upon the overall ratings of all memes which have not been seen by the user. Using the filter button, the application also allows the user to focus on a specific category.
Where Instagram has a Heart button and Facebook has Like and Love buttons, InstaMeme takes things a step further to allow the following responses for a provided meme:
= this meme should become viral
= very funny
= okay
= not funny at all
Why GraphQL?
Graph databases are a great solution when the relationships (edges) between your data (nodes) are just as important as the data itself. This is the core reason why social media applications utilize this database type.
The same principles will apply with the InstaMeme application:
Each user will maintain a profile.
Memes are uploaded and rated by the user's profile.
Each meme will require a category for classification.
To provide a top-notch user experience, it would be nice for a profile to maintain relationships with the memes that have been uploaded or rated. The category options should have the ability to see which meme objects link to each category name. Each meme object should have a relationship with both the category and profile objects as well.
For this project, I wanted to leverage a single service to house the InstaMeme data and provide API URIs which can access the GraphQL data. I decided to go with Dgraph's fully-managed backend service, called Slash GraphQL. It's a hosted, native GraphQL solution, which can be utilized via their free tier (with limited data transfer) or 7-day free trial (credit card required) by using the following link:
Once I created the instameme
backend service, initializing my environment only took a few seconds:
Schema Creation and Relationships
Based upon the initial requirements noted above, I created the following schema in Dgraph Slash GraphQL to house the InstaMeme data:
xxxxxxxxxx
type Category {
name: String! @search(by:[hash]) @id
memes: [Meme] @hasInverse(field:category)
}
type Profile {
handle: String! @search(by:[hash]) @id
memes: [Meme] @hasInverse(field:created_by)
ratings: [Rating] @hasInverse(field:by)
}
type Meme {
image_url: String! @search(by:[fulltext]) @id
title: String
category: Category! @hasInverse(field:memes)
created_by: Profile! @hasInverse(field:memes)
rank: Float
ratings: [Rating] @hasInverse(field:about)
}
type Rating {
id: ID!
about: Meme!
by: Profile!
score: Int @search
}
type Query {
mostRecommendedMemes(handle: String): [Meme] @lambda
}
While the scheme is pretty straight-forward to understand, the following directives have been employed:
@id - used to annotate a field that represents a unique identifier coming from outside of Dgraph
@search - allows filtering on a field while querying for nodes
@hasInverse - is used to setup up two-way edges such that adding an edge in one direction automatically adds the one in the inverse direction
@lamba - allows custom JavaScript resolvers to be utilized
Creating a Lambda For Advanced Functionality
To present the highest-rated items first, the support team at Dgraph helped me to create the mostRecommendedMemes
Lambda function. Lambda provides a way to write your custom logic in JavaScript, integrate it with your GraphQL schema, and execute it using the GraphQL API. In fact, Lambda directive and custom directive are the most powerful features that Slash provides. This allows developers to build any business logic within Slash.
In this example, the Lambda utilizes the aggregate average of all the meme ratings. I continued down the Lambda path to introduce filtering out memes which were uploaded by a user or already rated with a score:
xxxxxxxxxx
async function mostRecommendedMemes({args, graphql}) {
console.log('started mostRecommendedMemes()', args);
const results = await graphql(`query mostRecommendedMemes {
queryMeme {
title
rank
image_url
category {
name
}
ratings {
score
by {
handle
}
}
created_by {
handle
}
ratingsAggregate {
count
scoreAvg
}
}
}`)
const memeList = results.data.queryMeme;
let filteredList = [];
if (memeList && memeList.length > 0) {
for (meme of memeList) {
if (meme.created_by.handle !== args.handle && canRate(meme.ratings, args.handle)) {
if (meme && meme.ratings && meme.ratingsAggregate != null && meme.ratingsAggregate.scoreAvg != null) {
meme.rank = meme.ratingsAggregate.scoreAvg;
} else {
meme.rank = 0;
}
filteredList.push(meme);
}
}
if (filteredList.length > 1) {
filteredList.sort((a, b) => {return b.rank - a.rank})
}
}
console.log('finished mostRecommendedMemes()', filteredList);
return filteredList;
}
function canRate(ratings, handle) {
console.log('canRate()', ratings, handle);
if (ratings && ratings.length > 0) {
for (rating of ratings) {
console.log('rating', rating);
if (rating.by.handle == handle) {
console.log(handle + ' already rated this meme with a score=' + rating.score + ', returning false');
return false;
}
}
console.log(handle + ' has not rated this meme, returning true');
return true;
} else {
console.log('no ratings, returning true');
return true;
}
}
self.addGraphQLResolvers({
"Query.mostRecommendedMemes": mostRecommendedMemes
})
By adding the business logic into the Lambda, the need to utilize another application server (or performing more work on the client side) is completely avoided.
Adding Data to Slash GraphQL
With the schema and lambda in place, I quickly populated some basic category information:
xxxxxxxxxx
mutation {
addCategory(input: [
{name: "animals"},
{name: "pop-culture"},
{name: "movies"},
{name: "music"},
{name: "sports"}]) {
category {
name
}
}
}
Next, a few unique User records can be created:
xxxxxxxxxx
mutation {
addProfile(input: [
{handle: "aUser"},
{handle: "jv"},
{handle: "matt"},
{handle: "MontyMeme"},
{handle: "russell"}]) {
profile {
handle
}
}
}
The Dgraph Slash GraphQL service provides an API Explorer user interface which allows an easy mechanism to perform data insertions into the GraphQL database.
Using the internet, I decided to use the following memes:
Using the original URLs for each image (which may not work over time), the meme data can be added to the Slash database using the following statement:
xxxxxxxxxx
mutation {
addMeme(input: [{
image_url: "https://imgs.classicfm.com/images/214366?crop=16_9&width=660&relax=1&signature=9AmHbXaI3PYP8iYH4JqaNChosWg=",
title: "Bernie: The Audition Panel Before You Even Start Singing",
category: {name: "pop-culture"},
created_by: {handle: "russell"}},
{
image_url: "https://media.makeameme.org/created/when-you-find-76a8a4d633.jpg",
title: "When You Find Out You Have No Money",
category: {name: "movies"},
created_by: {handle: "matt"}
},
{
image_url: "https://static.thehoneycombers.com/wp-content/uploads/sites/4/2020/03/Best-funny-Coronavirus-memes-2020-Honeycombers-Bali-9.jpg",
title: "When You Find Out Your Normal Daily LifeStyle Is Called 'Quarantine'",
category: {name: "pop-culture"},
created_by: {handle: "MontyMeme"}
},
{
image_url: "https://ruinmyweek.com/wp-content/uploads/2020/03/tiger-king-memes-1.jpg",
title: "Tiger King: The Real Criminal Is Whoever's Cutting Hair In Wynnewood",
category: {name: "pop-culture"},
created_by: {handle: "russell"}
}
])
{
meme {
image_url
title
category { name }
created_by { handle }
}
}
}
The above mutation was designed to insert data into the GraphQL database. If a larger data set was required to be added, query variables could be utilized for a cleaner approach. For more information, please review the following URL:
Next, I used the following grid to establish some existing ratings:
The ratings can be translated into mutations to insert data into Slash GraphQL:
xxxxxxxxxx
mutation {
addRating(input: [
{
about: {image_url: "https://imgs.classicfm.com/images/214366?crop=16_9&width=660&relax=1&signature=9AmHbXaI3PYP8iYH4JqaNChosWg="},
by: {handle: "aUser"},
score: 4
},
{
about: {image_url: "https://imgs.classicfm.com/images/214366?crop=16_9&width=660&relax=1&signature=9AmHbXaI3PYP8iYH4JqaNChosWg="},
by: {handle: "matt"},
score: 4
},
{
about: {image_url: "https://imgs.classicfm.com/images/214366?crop=16_9&width=660&relax=1&signature=9AmHbXaI3PYP8iYH4JqaNChosWg="},
by: {handle: "MontyMeme"},
score: 4
},
{
about: {image_url: "https://media.makeameme.org/created/when-you-find-76a8a4d633.jpg"},
by: {handle: "aUser"},
score: 3
},
{
about: {image_url: "https://media.makeameme.org/created/when-you-find-76a8a4d633.jpg"},
by: {handle: "MontyMeme"},
score: 4
},
{
about: {image_url: "https://media.makeameme.org/created/when-you-find-76a8a4d633.jpg"},
by: {handle: "russell"},
score: 4
},
{
about: {image_url: "https://static.thehoneycombers.com/wp-content/uploads/sites/4/2020/03/Best-funny-Coronavirus-memes-2020-Honeycombers-Bali-9.jpg"},
by: {handle: "jv"},
score: 4
},
{
about: {image_url: "https://static.thehoneycombers.com/wp-content/uploads/sites/4/2020/03/Best-funny-Coronavirus-memes-2020-Honeycombers-Bali-9.jpg"},
by: {handle: "matt"},
score: 3
},
{
about: {image_url: "https://static.thehoneycombers.com/wp-content/uploads/sites/4/2020/03/Best-funny-Coronavirus-memes-2020-Honeycombers-Bali-9.jpg"},
by: {handle: "russell"},
score: 3
},
{
about: {image_url: "https://ruinmyweek.com/wp-content/uploads/2020/03/tiger-king-memes-1.jpg"},
by: {handle: "aUser"},
score: 3
},
{
about: {image_url: "https://ruinmyweek.com/wp-content/uploads/2020/03/tiger-king-memes-1.jpg"},
by: {handle: "matt"},
score: 2
},
{
about: {image_url: "https://ruinmyweek.com/wp-content/uploads/2020/03/tiger-king-memes-1.jpg"},
by: {handle: "MontyMeme"},
score: 4
}
])
{
rating {
about { image_url }
by { handle }
score
}
}
}
At this point, a base dataset exists to demonstrate the InstaMeme prototype.
Interacting with Slash GraphQL
Since Dgraph's Slash GraphQL provides GraphQL as a service, all the necessary service interaction to communicate with the GraphQL API is enabled and ready as soon as I created a new backend service called "instameme" on https://slash.dgraph.io/.
Using the built-in functionality of Slash GraphQL, the InstaMeme application can simply make the following query against the Dgraph Slash GraphQL service. In fact, the only requirement for the InstaMeme client is that it can make GraphQL API calls to the Dgraph SaaS.
To retrieve the most recommended memes for my user handle (“jv”), I submit the following query to the Slash GraphQL service:
xxxxxxxxxx
query MyQuery {
mostRecommendedMemes(handle: "jv") {
title
rank
image_url
}
}
This only returns the three memes which I have not yet rated:
xxxxxxxxxx
{
"data": {
"mostRecommendedMemes": [
{
"title": "Bernie: The Audition Panel Before You Even Start Singing",
"rank": 4,
"image_url": "https://imgs.classicfm.com/images/214366?crop=16_9&width=660&relax=1&signature=9AmHbXaI3PYP8iYH4JqaNChosWg="
},
{
"title": "When You Find Out You Have No Money",
"rank": 3.666667,
"image_url": "https://media.makeameme.org/created/when-you-find-76a8a4d633.jpg"
},
{
"title": "Tiger King: The Real Criminal Is Whoever's Cutting Hair In Wynnewood",
"rank": 3,
"image_url": "https://ruinmyweek.com/wp-content/uploads/2020/03/tiger-king-memes-1.jpg"
}
]
},
"extensions": {
"tracing": {
"version": 1,
"startTime": "2021-02-21T20:19:15.13150208Z",
"endTime": "2021-02-21T20:19:15.169366131Z",
"duration": 37864074
}
}
}
Notice that Slash GraphQL sorts the results from the highest rank to the lowest rank.
If I were to pass in the handle of a new user (newUserPerson
), the request would appear as:
xxxxxxxxxx
query MyQuery {
mostRecommendedMemes(handle: "newUserPerson") {
title
rank
image_url
}
}
Since none of the existing memes were uploaded by newUserPerson
or ranked, all four meme records will appear (in the correct order):
xxxxxxxxxx
{
"data": {
"mostRecommendedMemes": [
{
"title": "Bernie: The Audition Panel Before You Even Start Singing",
"rank": 4,
"image_url": "https://imgs.classicfm.com/images/214366?crop=16_9&width=660&relax=1&signature=9AmHbXaI3PYP8iYH4JqaNChosWg="
},
{
"title": "When You Find Out You Have No Money",
"rank": 3.666667,
"image_url": "https://media.makeameme.org/created/when-you-find-76a8a4d633.jpg"
},
{
"title": "When You Find Out Your Normal Daily LifeStyle Is Called 'Quarantine'",
"rank": 3.333333,
"image_url": "https://static.thehoneycombers.com/wp-content/uploads/sites/4/2020/03/Best-funny-Coronavirus-memes-2020-Honeycombers-Bali-9.jpg"
},
{
"title": "Tiger King: The Real Criminal Is Whoever's Cutting Hair In Wynnewood",
"rank": 3,
"image_url": "https://ruinmyweek.com/wp-content/uploads/2020/03/tiger-king-memes-1.jpg"
}
]
},
"extensions": {
"tracing": {
"version": 1,
"startTime": "2021-02-21T20:20:59.525924536Z",
"endTime": "2021-02-21T20:20:59.540636745Z",
"duration": 14712230
}
}
}
This is just a simple example of how the flexibility and power of a GraphQL database can meet the needs of a social media application, like the InstaMeme application.
The Point Where Slash GraphQL Saves Me
Using Dgraph Slash GraphQL, I was able to initialize a new backend service, create a schema, and insert sample data for prototyping in a very short time. At no cost, I was able to focus on prototyping my idea rather than worrying about multiple service layers.
If desired, I could have elected to use the open-source version of Dgraph's graph database, by selecting one of the local options at the following URL:
Schema updates are always quick and easy, which is a huge benefit of using GraphQL over alternate solutions.
In fact, in my early design, I had not considered the importance of the category object. I simply updated and submitted my schema enhancements in a matter of minutes, then returned my focus on application development. I also added the "title" attribute to the meme object, while I was writing this article because I felt it would be difficult to keep track of each unique URL.
If I was using an RDBMS like Oracle or MS SQL, this simple change could have disrupted my entire object model, causing me days of refactoring and unit testing. But with Slash GraphQL, it took just a few minutes.
Conclusion
As I continue to dive deeper into GraphQL, I reflect on prior projects which would benefit from a graph database aspect. This does not mean a conversion from a relational database to a graph database, but a coexistence—where each technology is the best solution. In the 30 years I have been an IT professional, I have seen this same approach with client and service frameworks. There is no reason why the database layer should not maintain the same view.
Have a really great day!
Opinions expressed by DZone contributors are their own.
Comments