GraphQL vs. REST - Tutorial GraphQL
Diterbitkan: 2022-03-11Anda mungkin pernah mendengar tentang anak baru di sekitar blok: GraphQL. Jika tidak, GraphQL, dengan kata lain, adalah cara baru untuk mengambil API, sebuah alternatif untuk REST. Ini dimulai sebagai proyek internal di Facebook, dan karena open source, telah mendapatkan banyak daya tarik.
Tujuan artikel ini adalah untuk membantu Anda melakukan transisi yang mudah dari REST ke GraphQL, apakah Anda sudah memutuskan untuk GraphQL atau Anda hanya ingin mencobanya. Tidak diperlukan pengetahuan sebelumnya tentang GraphQL, tetapi beberapa keakraban dengan REST API diperlukan untuk memahami artikel.
Bagian pertama artikel akan dimulai dengan memberikan tiga alasan mengapa saya pribadi menganggap GraphQL lebih unggul dari REST. Bagian kedua adalah tutorial tentang cara menambahkan titik akhir GraphQL di back-end Anda.
Graphql vs. REST: Mengapa Menjatuhkan REST?
Jika Anda masih ragu apakah GraphQL cocok untuk kebutuhan Anda atau tidak, gambaran umum yang cukup luas dan objektif tentang "REST vs. GraphQL" diberikan di sini. Namun, untuk tiga alasan utama saya menggunakan GraphQL, baca terus.
Alasan 1: Performa Jaringan
Katakanlah Anda memiliki sumber daya pengguna di bagian belakang dengan nama depan, nama belakang, email, dan 10 bidang lainnya. Pada klien, Anda biasanya hanya membutuhkan beberapa di antaranya.
Melakukan panggilan REST pada titik akhir /users
memberi Anda kembali semua bidang pengguna, dan klien hanya menggunakan yang dibutuhkannya. Jelas ada beberapa pemborosan transfer data, yang mungkin menjadi pertimbangan pada klien seluler.
GraphQL secara default mengambil data sekecil mungkin. Jika Anda hanya membutuhkan nama depan dan belakang pengguna Anda, Anda menentukannya dalam kueri Anda.
Antarmuka di bawah ini disebut GraphiQL, yang seperti penjelajah API untuk GraphQL. Saya membuat proyek kecil untuk tujuan artikel ini. Kode di-host di GitHub, dan kita akan membahasnya di bagian kedua.
Di panel kiri antarmuka adalah kueri. Di sini, kami mengambil semua pengguna—kami akan melakukan GET /users
dengan REST—dan hanya mendapatkan nama depan dan belakang mereka.
Pertanyaan
query { users { firstname lastname } }
Hasil
{ "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }
Jika kami ingin mendapatkan email juga, menambahkan baris "email" di bawah "nama belakang" akan berhasil.
Beberapa back-end REST memang menawarkan opsi seperti /users?fields=firstname,lastname
untuk mengembalikan sebagian sumber daya. Untuk apa nilainya, Google merekomendasikannya. Namun, ini tidak diterapkan secara default, dan membuat permintaan hampir tidak dapat dibaca, terutama saat Anda memasukkan parameter kueri lainnya:
-
&status=active
untuk memfilter pengguna aktif -
&sort=createdAat
untuk mengurutkan pengguna berdasarkan tanggal pembuatannya -
&sortDirection=desc
karena Anda jelas membutuhkannya -
&include=projects
untuk menyertakan proyek pengguna
Parameter kueri ini adalah tambalan yang ditambahkan ke REST API untuk meniru bahasa kueri. GraphQL di atas segalanya adalah bahasa kueri, yang membuat permintaan menjadi ringkas dan tepat sejak awal.
Alasan 2: Pilihan Desain “Sertakan vs. Titik Akhir”
Mari kita bayangkan kita ingin membangun alat manajemen proyek sederhana. Kami memiliki tiga sumber daya: pengguna, proyek, dan tugas. Kami juga mendefinisikan hubungan berikut antara sumber daya:
Berikut adalah beberapa titik akhir yang kami paparkan kepada dunia:
Titik akhir | Keterangan |
---|---|
GET /users | Daftar semua pengguna |
GET /users/:id | Dapatkan pengguna tunggal dengan id :id |
GET /users/:id/projects | Dapatkan semua proyek dari satu pengguna |
Titik akhir sederhana, mudah dibaca, dan terorganisir dengan baik.
Hal-hal menjadi lebih rumit ketika permintaan kami menjadi lebih kompleks. Mari kita ambil titik akhir GET /users/:id/projects
: Katakanlah saya hanya ingin menampilkan judul proyek di halaman beranda, tetapi proyek+tugas di dasbor, tanpa membuat beberapa panggilan REST. saya akan menelepon:
-
GET /users/:id/projects
untuk halaman beranda. -
GET /users/:id/projects?include=tasks
(misalnya) di halaman dasbor sehingga back-end menambahkan semua tugas terkait.
Sudah menjadi praktik umum untuk menambahkan parameter kueri ?include=...
agar ini berfungsi, dan bahkan direkomendasikan oleh spesifikasi JSON API. Parameter kueri seperti ?include=tasks
masih dapat dibaca, tetapi tidak lama kemudian, kita akan berakhir dengan ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author
.
Dalam hal ini, apakah akan lebih bijaksana untuk membuat titik akhir /projects
untuk melakukan ini? Sesuatu seperti /projects?userId=:id&include=tasks
, karena kita akan memiliki satu tingkat hubungan yang lebih sedikit untuk disertakan? Atau, sebenarnya, titik akhir /tasks?userId=:id
mungkin juga berfungsi. Ini bisa menjadi pilihan desain yang sulit, bahkan lebih rumit jika kita memiliki hubungan banyak ke banyak.
GraphQL menggunakan pendekatan include
di mana-mana. Ini membuat sintaks untuk mengambil hubungan kuat dan konsisten.
Berikut ini contoh pengambilan semua proyek dan tugas dari pengguna dengan id 1.
Pertanyaan
{ user(id: 1) { projects { name tasks { description } } } }
Hasil
{ "data": { "user": { "projects": [ { "name": "Migrate from REST to GraphQL", "tasks": [ { "description": "Read tutorial" }, { "description": "Start coding" } ] }, { "name": "Create a blog", "tasks": [ { "description": "Write draft of article" }, { "description": "Set up blog platform" } ] } ] } } }
Seperti yang Anda lihat, sintaks kueri mudah dibaca. Jika kami ingin masuk lebih dalam dan menyertakan tugas, komentar, gambar, dan penulis, kami tidak akan berpikir dua kali tentang cara mengatur API kami. GraphQL memudahkan untuk mengambil objek yang kompleks.
Alasan 3: Mengelola Berbagai Jenis Klien
Saat membangun back-end, kami selalu memulai dengan mencoba membuat API dapat digunakan secara luas oleh semua klien. Namun klien selalu ingin menelepon lebih sedikit dan mengambil lebih banyak. Dengan penyertaan mendalam, sumber daya sebagian, dan pemfilteran, permintaan yang dibuat oleh klien web dan seluler mungkin sangat berbeda satu sama lain.
Dengan REST, ada beberapa solusi. Kita dapat membuat titik akhir khusus (yaitu, titik akhir alias, misalnya /mobile_user
), representasi khusus ( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json
), atau bahkan klien -API spesifik (seperti yang pernah dilakukan Netflix). Ketiganya membutuhkan usaha ekstra dari tim pengembangan back-end.
GraphQL memberikan lebih banyak kekuatan untuk klien. Jika klien membutuhkan permintaan yang kompleks, itu akan membuat kueri yang sesuai itu sendiri. Setiap klien dapat menggunakan API yang sama secara berbeda.
Bagaimana Memulai dengan GraphQL
Dalam sebagian besar perdebatan tentang “GraphQL vs. REST” hari ini, orang berpikir bahwa mereka harus memilih salah satu dari keduanya. Ini tidak benar.
Aplikasi modern umumnya menggunakan beberapa layanan berbeda, yang mengekspos beberapa API. Kami benar-benar dapat menganggap GraphQL sebagai gateway atau pembungkus untuk semua layanan ini. Semua klien akan mencapai titik akhir GraphQL, dan titik akhir ini akan mencapai lapisan basis data, layanan eksternal seperti ElasticSearch atau Sendgrid, atau titik akhir REST lainnya.
Cara kedua untuk menggunakan keduanya adalah dengan memiliki titik akhir /graphql
terpisah pada REST API Anda. Ini sangat berguna jika Anda sudah memiliki banyak klien yang menggunakan REST API Anda, tetapi Anda ingin mencoba GraphQL tanpa mengorbankan infrastruktur yang ada. Dan inilah solusi yang sedang kami jajaki hari ini.
Seperti yang dikatakan sebelumnya, saya akan mengilustrasikan tutorial ini dengan proyek contoh kecil, tersedia di GitHub. Ini adalah alat manajemen proyek yang disederhanakan, dengan pengguna, proyek, dan tugas.
Teknologi yang digunakan untuk proyek ini adalah Node.js dan Express untuk web server, SQLite sebagai database relasional, dan Sequelize sebagai ORM. Tiga model—pengguna, proyek, dan tugas—didefinisikan dalam folder models
. Titik akhir REST /api/users
, /api/projects
dan /api/tasks
diekspos ke dunia, dan ditentukan di folder rest
.

Perhatikan bahwa GraphQL dapat diinstal pada semua jenis back-end dan database, menggunakan bahasa pemrograman apa pun. Teknologi yang digunakan di sini dipilih demi kesederhanaan dan keterbacaan.
Tujuan kami adalah membuat titik akhir /graphql
tanpa menghapus titik akhir REST. Titik akhir GraphQL akan mengenai database ORM secara langsung untuk mengambil data, sehingga benar-benar independen dari logika REST.
Jenis
Model data diwakili dalam GraphQL oleh types , yang sangat diketik. Harus ada pemetaan 1-ke-1 antara model Anda dan tipe GraphQL. Jenis User
kami adalah:
type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }
Pertanyaan
Kueri menentukan kueri apa yang dapat Anda jalankan di GraphQL API Anda. Menurut konvensi, harus ada RootQuery
, yang berisi semua kueri yang ada. Saya juga menunjukkan padanan REST dari setiap kueri:
type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }
Mutasi
Jika kueri adalah permintaan GET
, mutasi dapat dilihat sebagai permintaan POST
/ PATCH
/ PUT
/ DELETE
(walaupun sebenarnya itu adalah versi kueri yang disinkronkan).
Dengan konvensi, kami menempatkan semua mutasi kami di RootMutation
:
type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }
Perhatikan bahwa kami memperkenalkan tipe baru di sini, yang disebut UserInput
, ProjectInput
, dan TaskInput
. Ini adalah praktik umum dengan REST juga, untuk membuat model data input untuk membuat dan memperbarui sumber daya. Di sini, tipe UserInput
kami adalah tipe User
kami tanpa bidang id
dan projects
, dan perhatikan input
kata kunci alih-alih type
:
input UserInput { firstname: String lastname: String email: String }
Skema
Dengan tipe, kueri, dan mutasi, kami mendefinisikan skema GraphQL , yang ditunjukkan oleh titik akhir GraphQL kepada dunia:
schema { query: RootQuery mutation: RootMutation }
Skema ini diketik dengan kuat dan itulah yang memungkinkan kami memiliki pelengkapan otomatis yang praktis di GraphiQL.
Penyelesai
Sekarang setelah kita memiliki skema publik, sekarang saatnya untuk memberi tahu GraphQL apa yang harus dilakukan ketika setiap kueri/mutasi ini diminta. Resolver melakukan kerja keras; mereka dapat, misalnya:
- Mencapai titik akhir REST internal
- Hubungi layanan mikro
- Tekan lapisan database untuk melakukan operasi CRUD
Kami memilih opsi ketiga di aplikasi contoh kami. Mari kita lihat file resolver kami:
const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }
Ini berarti, jika kueri user(id: ID!)
diminta pada GraphQL, maka kami mengembalikan User.findById()
, yang merupakan fungsi Sequelize ORM, dari database.
Bagaimana dengan bergabung dengan model lain dalam permintaan? Nah, kita perlu mendefinisikan lebih banyak resolver:
User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */
Jadi ketika kami meminta bidang projects
dalam tipe User
di GraphQL, gabungan ini akan ditambahkan ke kueri basis data.
Dan akhirnya, resolver untuk mutasi:
RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }
Anda dapat bermain-main dengan ini di sini. Demi menjaga data di server tetap bersih, saya menonaktifkan resolver untuk mutasi, yang berarti bahwa mutasi tidak akan melakukan operasi buat, perbarui, atau hapus apa pun dalam database (dan dengan demikian mengembalikan null
pada antarmuka).
Pertanyaan
query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }
Hasil
{ "data": { "user": { "firstname": "Alicia", "lastname": "Smith", "projects": [ { "name": "Email Marketing Campaign", "tasks": [ { "description": "Get list of users" }, { "description": "Write email template" } ] }, { "name": "Hire new developer", "tasks": [ { "description": "Find candidates" }, { "description": "Prepare interview" } ] } ] } } }
Mungkin perlu beberapa saat untuk menulis ulang semua jenis, kueri, dan resolver untuk aplikasi Anda yang sudah ada. Namun, ada banyak alat untuk membantu Anda. Misalnya, ada alat yang menerjemahkan skema SQL ke skema GraphQL, termasuk resolver!
Menyatukan Semuanya
Dengan skema yang terdefinisi dengan baik dan resolver tentang apa yang harus dilakukan pada setiap kueri skema, kita dapat memasang titik akhir /graphql
di back-end kita:
// Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));
Dan kita dapat memiliki antarmuka GraphiQL yang tampak bagus di back-end kita. Untuk membuat permintaan tanpa GraphiQL, cukup salin URL permintaan, dan jalankan dengan cURL, AJAX, atau langsung di browser. Tentu saja, ada beberapa klien GraphQL untuk membantu Anda membuat kueri ini. Lihat di bawah untuk beberapa contoh.
Apa berikutnya?
Tujuan artikel ini adalah untuk memberi Anda gambaran tentang tampilan GraphQL dan menunjukkan kepada Anda bahwa sangat mungkin untuk mencoba GraphQL tanpa membuang infrastruktur REST Anda. Cara terbaik untuk mengetahui apakah GraphQL sesuai dengan kebutuhan Anda adalah dengan mencobanya sendiri. Saya harap artikel ini akan membuat Anda menyelam.
Ada banyak fitur yang belum kita bahas dalam artikel ini, seperti pembaruan waktu nyata, pengelompokan sisi server, otentikasi, otorisasi, caching sisi klien, pengunggahan file, dll. Sumber daya yang sangat baik untuk mempelajari tentang fitur-fitur ini adalah Bagaimana GraphQL.
Di bawah ini adalah beberapa sumber berguna lainnya:
Alat sisi server | Keterangan |
---|---|
graphql-js | Implementasi referensi GraphQL. Anda dapat menggunakannya dengan express-graphql untuk membuat server. |
graphql-server | Server GraphQL all-in-one yang dibuat oleh tim Apollo. |
Implementasi untuk platform lain | Ruby, PHP, dll. |
Alat sisi klien | Keterangan |
---|---|
Menyampaikan | Kerangka kerja untuk menghubungkan React dengan GraphQL. |
apollo-klien. | Klien GraphQL dengan binding untuk React, Angular 2, dan kerangka kerja front-end lainnya. |
Kesimpulannya, saya percaya bahwa GraphQL lebih dari sekadar sensasi. Itu tidak akan menggantikan REST besok dulu, tetapi itu memang menawarkan solusi berkinerja untuk masalah asli. Ini relatif baru, dan praktik terbaik masih berkembang, tetapi ini jelas merupakan teknologi yang akan kita dengar dalam beberapa tahun ke depan.