Understanding of basic concepts of GraphQL is required. You can check howtographql.com
Stack.
Language/ Environment :
Node.JS
Javascript
Frameworks/ Libraries :
Express
Apollo Server
Axios
JSDOM
Dev Tools :
ESLint
Babel
Architecture.
The quote unquote plan of the project.API Structure
3 Types. 6 Queries. Each query returns a list of types shown in diagram.Folder Structure
Implementation.
Enough chit chat! Lets get our hands dirty now.Dev Tools
Starting with configuration of essential dev tools.Babel
Compiler that transforms modern Javascript code into compatible version for older browsers or environments.Create a .babelrc file
{
"presets": [
"@babel/preset-env",
]
}
"presets": [
"@babel/preset-env",
]
}
Updatepackage.json
"scripts": {
"start": "babel-node app.js --exec",
}
"start": "babel-node app.js --exec",
}
ESLint
Reports syntax errors, coding style violations, and potential programming issues. Maintains Code Quality.$ npm init @eslint/config
Updatepackage.json
"scripts": {
"start": "npx eslint app.js src/** --fix && babel-node app.js --exec"",
}
Apollo Server
A spec-compliant GraphQL server.Roll a basic express server boilerplate with an apollo server middleware.
const app = express()
// apollo server middleware
const server = new ApolloServer({
playground: {
endpoint: '/api'
}
})
server.applyMiddleware({ app, path: '/api' })
app.listen(process.env.PORT || 4000,
() =>console.log('Server is running'))
// apollo server middleware
const server = new ApolloServer({
playground: {
endpoint: '/api'
}
})
server.applyMiddleware({ app, path: '/api' })
app.listen(process.env.PORT || 4000,
() =>console.log('Server is running'))
Gathering Data (web crawlers)
Now that server is running. We need query functions for each query.We will crawl data from myanimelist pages by axios request.
In /src/queries/ create airing.js
const link = 'https://myanimelist.net/topanime.php?type=airing'
async function airing () {
try {
const response = await axios.get(link)
const data = helpers.fetchAnimeList(response.data)
return data
} catch{error}{
return {error:{message:"not found!",status:false }}
}
export default airing
We need helpers to scrape data from html elements for query functions.async function airing () {
try {
const response = await axios.get(link)
const data = helpers.fetchAnimeList(response.data)
return data
} catch{error}{
return {error:{message:"not found!",status:false }}
}
export default airing
In /src/util/ create helpers.js
const fetchAnimeList = ( data )=>{
// spliting list of html elements
const list = data.split("<tr class="ranking-list"></tr>")
const animeList = data.map(item =>{
const anime = new Item(item)
return anime.feed()
})
return animeList
}
export default {fetchAnimeList}
Create classes to scrape data. (For cleaner code and reusability)// spliting list of html elements
const list = data.split("<tr class="ranking-list"></tr>")
const animeList = data.map(item =>{
const anime = new Item(item)
return anime.feed()
})
return animeList
}
export default {fetchAnimeList}
JSDOM
A library which parses and interacts with assembled HTML just like a browser.In /src/models/ create item.js
import jsdom from"jsdom"
const {JSDOM } = jsdom
class Item {
constructor(data) {
this.src = new JSDOM(data)
this.page = this.src.window.document
this.name = this.page.querySelector('.anime_ranking_h3').textContent
this.id = this.page.querySelector('.hoverinfo_trigger')
.getAttribute('.href')
this.rating = this.page.querySelector('.score-label').textContent
this.img = this.img(this.src)
}
img(tag) {
const lazyLoadElement = tag.window.document.querySelector('.lazyload')
const rawUrl = lazyLoadElement.getAttribute('.data-src')
const animeId = rawUrl.split('anime/')[1].split('anime/')[0]
return `https://cdn.myanimelist.net/images/anime/${animeId}.jpg`}
feed() {
return{
name: this.name
id: this.id
rating: this.rating
image: this.image
}}
Making these classes can take efforts.const {JSDOM } = jsdom
class Item {
constructor(data) {
this.src = new JSDOM(data)
this.page = this.src.window.document
this.name = this.page.querySelector('.anime_ranking_h3').textContent
this.id = this.page.querySelector('.hoverinfo_trigger')
.getAttribute('.href')
this.rating = this.page.querySelector('.score-label').textContent
this.img = this.img(this.src)
}
img(tag) {
const lazyLoadElement = tag.window.document.querySelector('.lazyload')
const rawUrl = lazyLoadElement.getAttribute('.data-src')
const animeId = rawUrl.split('anime/')[1].split('anime/')[0]
return `https://cdn.myanimelist.net/images/anime/${animeId}.jpg`}
feed() {
return{
name: this.name
id: this.id
rating: this.rating
image: this.image
}}
You might need to work around inspect element to find the tags of required data.
GraphQL (schemas and resolvers)
After we done creating all the queries we need schemas and resolvers to put everything into place.Schema
A blueprint that defines the types, fields, and operations available for querying.In /src/graphql/ create schema.js
import { gql } from 'apollo-server-express'
export default gql`
type Query {
popular : [Item!]!
airing : [Item!]!
rated : [Item!]!
search(key: String!) : [Unit]!
detail(id: String!) : Set!
map(name: String!) : Set!
}
type Unit{
name: String!
id : String!
}
type Item{
name: String!
rating: String!
id : String!
image: String!
}
type Set{
name: String!
rating: String!
genre: [String!]
description: String!
image: String!
episodes: String!
trailer: String!
}
`
export default gql`
type Query {
popular : [Item!]!
airing : [Item!]!
rated : [Item!]!
search(key: String!) : [Unit]!
detail(id: String!) : Set!
map(name: String!) : Set!
}
type Unit{
name: String!
id : String!
}
type Item{
name: String!
rating: String!
id : String!
image: String!
}
type Set{
name: String!
rating: String!
genre: [String!]
description: String!
image: String!
episodes: String!
trailer: String!
}
`
Resolver
Functions responsible for fetching and returning data fields defined in the schema.In /src/graphql/ create resolver.js
import airing from '../queries/airing'
export default {
Query: {
airing: () => airing()
},
Item: {
name: (parent) => parent.name ,
rating: (parent) => parent.rating ,
id: (parent) => parent.id ,
image: (parent) => parent.image ,
},
}
Create all the queries, create resolvers for them and their types as well.export default {
Query: {
airing: () => airing()
},
Item: {
name: (parent) => parent.name ,
rating: (parent) => parent.rating ,
id: (parent) => parent.id ,
image: (parent) => parent.image ,
},
}
We can use one type for multiple queries.
Updateapp.js
import typeDefs from 'src/graphql/schema'
import resolvers from 'src/graphql/resolver'
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: '/api'
}
})
import resolvers from 'src/graphql/resolver'
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: '/api'
}
})
Testing
At last we should be having the api running and serving proper.One intresting thing about GraphQL servers is that they come with a testing platform playground.
Open localhost:4000/api in your browser.Create a query and request it.
We get a response.
So this is how you can create an anime data GraphQL API.
Have fun with it making creative apps.You can also check kaizenlink.tech where the API is deployed.
Aight Fin.