Contoh Penerapan GraphQL Dengan Vue JS
GraphQL adalah bahasa kueri untuk API Anda, dan runtime sisi server untuk mengeksekusi kueri. Satu titik akhir dapat mengembalikan data tentang banyak sumber daya, yang membuatnya sangat cocok untuk aplikasi satu halaman Vue.js.
Artikel ini akan membahas cara membangun GraphQL API dari awal, serta mendefinisikan dan mengimplementasikan kueri dan mutasi dengan tipe khusus. Saya akan menggunakan Node.js untuk server GraphQL, dan membuat permintaan serta menampilkan hasilnya menggunakan aplikasi satu halaman Vue.js.
Kode sumber untuk artikel ini ada di sini.
Pengantar
Layanan GraphQL dibuat dengan mendefinisikan jenis dan bidang, kemudian menyediakan fungsi untuk setiap bidang pada setiap jenis. Contoh kanonik dari dokumentasi GraphQL adalah:
type Query { // define the query
me: User // define the fields
}type User { // define the type
id: ID
name: String
}function Query_me(request) { // define the function
return request.auth.user
}
Di atas adalah bagaimana kami mengimplementasikan kueri, tipe khusus, dan titik akhir menggunakan GraphQL. Kueri sisi klien yang cocok terlihat seperti ini:
{
me {
name
}
}
yang mengembalikan:
{
"me": {
"name": "username"
}
}
Dokumentasi GraphQL resmi sangat bagus, tetapi saya merasa kurang dalam contoh praktis tentang cara mengkueri data menggunakan klien HTTP standar, dan mengintegrasikannya ke dalam aplikasi Vue.js saya, jadi saya akan melakukan hal berikut di artikel ini. Kami akan menggunakan vue-cli baru untuk membuat perancah proyek Vue untuk mengikutinya.
Memulai
Instal vue-cli dengan menjalankan:
npm install -g @vue/cli@latest
Buat proyek baru dengan menjalankan:
vue create graphql-example
dan pergi dengan default dengan memilih default (babel, eslint). Satu ton modul anggukan akan dipasang. Kita juga perlu membuat folder untuk server API, jadi jalankan perintah berikut setelah cd ke dalam proyek (cd graphql-example)
mkdir server
npm install express express-graphql graphql --save
Kami menambahkan graphql, serta express dan express-graphql, yang merupakan lapisan tipis yang menerapkan beberapa praktik terbaik dan pedoman untuk melayani kueri melalui HTTP.
Permintaan Dasar
Mari siapkan kueri sederhana untuk memastikan semuanya berfungsi, dan lihat seperti apa server GraphQL. Di dalam server/index.js, memerlukan beberapa modul:
const express = require('express')
const { graphql, buildSchema } = require('graphql')
const graphqlHTTP = require('express-graphql')
const cors = require('cors')
- express dan express-graphql akan memungkinkan kami menanggapi permintaan HTTP
- buildSchema digunakan untuk mendefinisikan tipe (lebih cepat)
-
cors akan memungkinkan kami membuat permintaan dari aplikasi Vue kami, yang akan berjalan pada port
8080
, ke server yang berjalan pada port4000
Hal berikutnya yang harus dilakukan adalah menentukan skema — jenis kueri dan jenis apa yang akan digunakan server. Skema pertama kami pada dasarnya adalah "halo dunia" dari GraphQL:
const schema = buildSchema(`
type Query {
language: String
}
`)
Kami mendefinisikan jenis Query yang disebut bahasa. Ini mengembalikan sebuah String. GraphQL diketik secara statis — bidang memiliki tipe, dan jika ada sesuatu yang tidak cocok, dan kesalahan akan muncul.
Tidak seperti REST API, Graph API hanya memiliki satu titik akhir, yang merespons semua permintaan. Ini disebut pemecah masalah. Saya akan memanggil rootValue milik saya, dan menyertakan implementasi untuk kueri bahasa:
const rootValue = {
language: () => 'GraphQL'
}
bahasa baru saja mengembalikan sebuah String
. Jika kami mengembalikan tipe yang berbeda, misalnya 1 atau {}
, kesalahan akan muncul, karena ketika kami mendeklarasikan bahasa dalam skema, kami menetapkan String
akan dikembalikan.
Langkah terakhir adalah membuat aplikasi ekspres, dan memasang resolver, rootValue
, dan skema
.
const app = express()
app.use(cors())app.use('/graphql', graphqlHTTP({
rootValue, schema, graphiql: true
}))app.listen(4000, () => console.log('Listening on 4000')
Sekarang mari kita implementasikan aplikasi Vue sisi klien, yang akan membuat permintaan.
Membuat permintaan
Buka src/App.vue
, dan hapus boilerplate. Sekarang seharusnya terlihat seperti ini:
<template>
<div id="app">
</div>
</template><script>
import axios from 'axios'export default {
name: 'app'
}
</script>
Kami juga mengimpor axios
, yang akan kami gunakan untuk membuat permintaan HTTP.
Secara default, graphqlHTTP
mendengarkan permintaan POST. Menurut rekomendasi dalam melayani melalui HTTP, kita harus menyertakan query
dan variabel di badan permintaan. Ini akan membawa kita ke permintaan berikut:
axios.post('http://localhost:4000/graphql', {
query: '{ language }'
})
Kueri harus berada di dalam kurung kurawal. Menambahkan tombol untuk memicu permintaan, dan variabel untuk menyimpan hasilnya, kita akan mendapatkan:
<template>
<div id="app">
<h3>Example 1</h3>
<div>
Data: {{ example1 }}
</div>
<button @click="getLanguage">Get Language</button>
<hr>
</div>
</template><script>
import axios from 'axios'export default {
name: 'app', data () {
return {
example1: ''
}
}, methods: {
async getLanguage () {
try {
const res = await axios.post(
'http://localhost:4000/graphql', {
query: '{ language }'
})
this.example1 = res.data.data.language
} catch (e) {
console.log('err', e)
}
}
}
}
</script>
Mari kita jalankan ini. Di satu terminal, mulai server GraphQL dengan node server . Di tempat lain, jalankan aplikasi Vue menggunakan npm run serve
. Kunjungi http://localhost:8080
. Jika semuanya berjalan dengan baik, Anda akan melihat:
Klik "Dapatkan Bahasa" akan mengembalikan dan merender hasilnya.
OK bagus. Sejauh ini kami:
- mendefinisikan skema
- membuat resolver, rootValue
- buat permintaan menggunakan aksioma, yang menyertakan query
Apa lagi yang bisa kita lakukan dengan GraphQL?
Jenis Kustom dengan Model
GraphQL memungkinkan kita mendefinisikan jenis kustom, dan objek untuk mewakili mereka dalam bahasa target — dalam hal ini, JavaScript, tetapi ada klien GraphQL untuk sebagian besar bahasa sisi server. Saya akan mendefinisikan tipe Champion dalam skema, dan mencocokkan kelas ES6 untuk menyimpan properti dan metode apa pun.
Pertama, perbarui skema:
const schema = buildSchema(`
type Query {
language: String
} type Champion {
name: String
attackDamage: Float
}
`)
Tidak ada yang terlalu menarik selain tipe baru, Float
. Selanjutnya kita dapat mendefinisikan kelas ES6 untuk mewakili tipe ini, dan menyimpan metode instan atau data tambahan apa pun. Saya akan mendefinisikan ini di file baru, server/champion.js
.
class Champion {
constructor(name, attackDamage) {
this.name = name
this.attackDamage = attackDamage
}
}module.exports = Champion
Tidak ada yang istimewa, hanya kelas ES6. Perhatikan bahwa kami memiliki nama
dan attackDamage
— bidang yang sama yang ditentukan dalam skema untuk Champion.
Sekarang, mari buat kueri lain yang menggunakan tipe Champion
. Skema yang diperbarui adalah sebagai berikut:
const schema = buildSchema(`
type Query {
language: String
getChampions: [Champion]
} type Champion {
name: String
attackDamage: Float
}
`)
getChampions
mengembalikan serangkaian Champion. Besar! Untuk menyelesaikan contoh ini, beberapa data tiruan dan titik akhir lainnya:
const champions = [
new Champion('Ashe', 100),
new Champion('Vayne', 200)
]const rootValue = {
language: () => 'GraphQL', getChampions: () => champions
}
Restart server dengan menekan ctrl+c
di terminal yang menjalankan server, dan jalankan node server
lagi. Mari kita verifikasi apakah ini berfungsi, dengan mengirimkan kueri dari klien.
Meminta bidang tertentu
Menanyakan getChampions
sedikit lebih menarik daripada bahasa. Kali ini hasilnya akan berisi tipe Champion yang ditentukan pengguna — dan bidang apa pun yang kami minta. GraphQL
mengharuskan kita untuk eksplisit di bidang mana yang kita inginkan. Misalnya query berikut:
{
getChampions
}
tidak akan berfungsi. Setidaknya satu bidang harus ditentukan. Kueri yang diperbarui:
{
getChampions {
name
}
}
Pengembalian:
{
"data": {
"getChampions": [
{
"name": "Ashe"
},
{
"name": "Vayne"
}
]
}
}
Perhatikan hanya nama yang dikembalikan! Jika kami menyertakan attackDamage, kami juga akan mendapatkannya. Kueri:
{
getChampions {
name
attackDamage
}
}
dan tanggapan:
{
"data": {
"getChampions": [
{
"name": "Ashe"
"attackDamage": 100
},
{
"name": "Vayne"
"attackDamage": 200
}
]
}
}
Menerapkan ini di aplikasi Vue sama mudahnya:
<template>
<div id="app">
<!-- ... --> <h3>Example 2</h3>
<div>
Data:
<div v-for="champion in champions">
{{ champion }}
</div>
</div>
<button @click="getChampions">Get Champions</button>
</div>
</template>export default {
name: 'app', data () {
return {
/* ... */,
champions: []
}
}, methods: {
/* ... */
async getChampions () {
const res = await axios.post(
'http://localhost:4000/graphql', {
query: `{
getChampions {
name
}
}`
})
this.champions = res.data.data
}
}
}
Pastikan Anda me-restart server dengan server node, jika Anda belum melakukannya. Tidak perlu me-restart aplikasi Vue, karena hot reload webpack akan otomatis diperbarui saat Anda menyimpan perubahan apa pun.
Mengklik "Dapatkan Juara" menghasilkan:
Melewati Argumen
getChampions mengembalikan semua juara. GraphQL juga mendukung argumen yang lewat, untuk mengembalikan subset data. Ini membutuhkan:
- objek variabel tambahan di badan POST
- memberi tahu permintaan sisi klien jenis argumen yang akan Anda parsing ke kueri dari variabel.
Mari kita terapkan kueri getChampionByName
. Seperti biasa, mulailah dengan definisi kueri:
const schema = buildSchema(`
type Query {
language: String
getChampions: [Champion]
getChampionByName(name: String!): Champion
} type Champion {
name: String
attackDamage: Float
}
`)
Perhatikan kita mendeklarasikan nama argumen, dan tipe String!. ! berarti argumen diperlukan.
Selanjutnya implementasinya:
const rootValue = {
language: () => 'GraphQL', getChampions: () => champions, getChampionByName: ({ name }) => {
return champions.find(x => x.name === name)
}
}
Tidak ada yang terlalu menarik — kami hanya menggunakan find
untuk mendapatkan juara yang sesuai. Perbaikan akan menambahkan beberapa penanganan kesalahan, dan membandingkan nama kasus mengabaikan.
Sekarang, implementasi sisi klien. Di sinilah segalanya menjadi sedikit lebih menarik. Saat meneruskan argumen, kita harus memberi nama kueri, dan mendeklarasikan argumen dengan tipe yang sesuai:
async getChampionByName () {
const res = await axios.post('http://localhost:4000/graphql', {
query: `
query GetChampionByName($championName: String!) {
getChampionByName(name: $championName) {
name
attackDamage
}
}`,
variables: {
championName: 'Ashe'
}
})
this.champion = res.data.data.getChampionByName
}
Baris demi baris:
-
kueri
GetChampionByName
adalah nama yang kami berikan untuk kueri. Ini bisa apa saja, tetapi harus deskriptif tentang apa yang dilakukan kueri. Dalam hal ini, karena kami hanya memanggilgetChampionByName
, saya menggunakan konvensi ketika namanya sama dengan kueri di sisi server, tetapi menggunakan huruf kapital untuk huruf pertama. Dalam aplikasi nyata, satu panggilan API mungkin menyertakan banyak operasi berbeda. Memberi nama kueri dapat membuat kode lebih mudah dipahami. -
($championName: String!)
berarti variabel harus berisinama champion
, dan ini bukan opsional. -
getChampionByName(name: $championName)
adalah kueri yang akan dieksekusi di sisi server. Argumen pertama, nama, harus menggunakan nilaichampionName
di objek variabel. -
Kami meminta nama dan
attackDamage
sebagai tanggapan.
Beberapa markup tambahan akan memungkinkan kami menampilkan hasilnya di aplikasi Vue (jangan lupa untuk me-restart server GraphQL):
<template>
<div> <!-- ... --> <h3>Example 4</h3>
Name: <input v-model="name">
<div>
Data:
{{ champion }}
</div>
<button @click="getChampionByName">Get Champion</button> </div>
</template><script>
import axios from 'axios'export default {
data () {
return {
/* ... */
champion: {}
}
},methods: { /* ... */ async getChampionByName () {
const res = await axios.post(
'http://localhost:4000/graphql', {
query: `
query GetChampionByName($championName: String!) {
getChampionByName(name: $championName) {
name
attackDamage
}
}`,
variables: {
championName: 'Ashe'
}
})
this.champion = res.data.data.getChampionByName
}
}
}
Memperbarui Catatan
Sejauh ini, kami baru saja mengambil data. Anda juga sering ingin memperbarui data, itulah sebabnya GraphQL juga menyediakan mutasi. Sintaks dan implementasinya tidak terlalu jauh dari apa yang telah kita bahas sejauh ini. Mari kita mulai dengan mendefinisikan mutasi:
const schema = buildSchema(`
type Query {
language: String
getChampions: [Champion]
getChampionByName(name: String!): Champion
} type Mutation {
updateAttackDamage(name: String!, attackDamage: Float): Champion
} type Champion {
name: String
attackDamage: Float
}
`)
Mutasi masuk dalam tipe Mutasi. Sintaks lainnya harus sudah familier pada titik ini. Kami mengembalikan catatan yang diperbarui, tipe Champion
. Implementasinya sama mudahnya:
const rootValue = {
language: () => 'GraphQL', getChampions: () => champions, getChampionByName: ({ name }) => {
return champions.find(x => x.name === name)
}, updateAttackDamage: ({ name, attackDamage = 150 }) => {
const champion = champions.find(x => x.name === name)
champion.attackDamage = attackDamage return champion
}
}
Dalam contoh yang lebih realistis, Anda mungkin menjalankan kueri SQL untuk memperbarui catatan dalam database, atau melakukan beberapa validasi. Kami harus mengembalikan tipe Champion
, karena kami menentukannya dalam deklarasi mutasi. GraphQL akan secara otomatis memilih bidang yang benar untuk dikembalikan, berdasarkan permintaan — kami akan menanyakan nama dan memperbarui attackDamage
, seperti yang ditunjukkan di bawah ini:
methods: {
/* ... */ async updateAttackDamage () {
const res = await axios.post('http://localhost:4000/graphql', {
query: `
mutation UpdateAttackDamage(
$championName: String!, $attackDamage: Float) {
updateAttackDamage(name: $championName, attackDamage: $attackDamage) {
name
attackDamage
}
}`,
variables: {
championName: this.name,
attackDamage: this.attack
}
})
this.updatedChampion = res.data.data.updateAttackDamage
}
}
Satu-satunya perbedaan nyata di sini adalah kami mendeklarasikan nama operasi sebagai tipe mutasi alih-alih tipe kueri.
Contoh yang diperbarui sepenuhnya adalah sebagai berikut:
<template>
<div>
<!-- ... -->
<h3>Example 4</h3>
Name: <input v-model="name">
Attack Damage: <input v-model.number="attack">
<div>
Data:
{{ updatedChampion }}
</div>
<button @click="updateAttackDamage">Update Champion</button> </div>
</template><script>
import axios from 'axios'export default { data () {
return {
/* ... */
updatedChampion: {},
attack: 5.5
}
}, methods: {
/* ... */
async updateAttackDamage () {
const res = await axios.post('http://localhost:4000/graphql', {
query: `
mutation UpdateAttackDamage($championName: String!, $attackDamage: Float) {
updateAttackDamage(name: $championName, attackDamage: $attackDamage) {
name
attackDamage
}
}`,
variables: {
championName: this.name,
attackDamage: this.attack
}
})
this.updatedChampion = res.data.data.updateAttackDamage
}
}
}
Seperti biasa, restart server GraphQL. Hasilnya adalah sebagai berikut:
Anda dapat mengklik "Dapatkan Juara", dan lihat apakah data disimpan dengan benar (seharusnya mengembalikan kerusakan serangan yang baru diperbarui):
Pengujian
Saya tidak melewati pengujian. Namun, menguji titik akhir sisi server adalah esay, karena ini hanyalah JavaScript biasa — cukup ekspor objek rootValue
, dan uji fungsi seperti biasanya. Saya akan mengeksplorasi pengujian GraplQL API di posting mendatang.
Kesimpulan
Ada banyak hal lain yang dapat dilakukan GraphQL. Baca lebih lanjut di situs resmi. Saya berharap untuk mengeksplorasi lebih banyak di posting mendatang. Ini adalah alternatif yang menyegarkan untuk REST, dan sangat cocok untuk aplikasi satu halaman berbasis Vue.js.