9 Building a Basic HTTP Server for a GraphQL Endpoint
9 Building a Basic HTTP Server for a GraphQL Endpoint
Introduction
GraphQL has become the new standard for modern APIs. Many developers want to try out or deploy a GraphQL endpoint without the hassle of heavy frameworks like Apollo Server or Relay. Sometimes all we need is a simple endpoint for a prototype, for mocking a backend, or for learning by doing. Whatever the reason, the core concept is the same: we need an HTTP server capable of receiving requests and responding with data based on a GraphQL query.
This article walks through how to build a basic HTTP server for GraphQL using Node.js and the graphql-js library. Our focus is not on fancy features, but on the core concept and implementation of a GraphQL endpoint: a request comes in over HTTP, the query is processed, and a response is sent back.
1. Prerequisites
To follow this tutorial, all you need is:
- Basic knowledge of JavaScript/Node.js
- Node.js >= v14
- The
graphqllibrary from npm
Installing GraphQL:
1npm init -y
2npm install graphqlFor the HTTP server, we can simply use Node’s built-in http module.
2. A Minimalist HTTP Server Structure
The main goal is to accept a POST request at /graphql, read the query from the request body, then process it and send the result back. There’s no need for complicated routing.
1const http = require('http');
2const { graphql, buildSchema } = require('graphql');
3
4// 1. Define schema & resolver
5const schema = buildSchema(`
6 type Query {
7 hello: String
8 }
9`);
10const rootValue = {
11 hello: () => 'Hello, world!'
12};
13
14// 2. HTTP server declaration
15const server = http.createServer(async (req, res) => {
16 if (req.method === 'POST' && req.url === '/graphql') {
17 let body = '';
18 req.on('data', chunk => body += chunk);
19 req.on('end', async () => {
20 try {
21 const { query, variables } = JSON.parse(body);
22
23 // 3. Core: GraphQL execution
24 const result = await graphql({
25 schema,
26 source: query,
27 rootValue,
28 variableValues: variables
29 });
30
31 // Response
32 res.writeHead(200, { 'Content-Type': 'application/json' });
33 res.end(JSON.stringify(result));
34 } catch (err) {
35 res.writeHead(400);
36 res.end(JSON.stringify({ errors: [{ message: err.message }] }));
37 }
38 });
39 } else {
40 res.writeHead(404);
41 res.end();
42 }
43});
44
45server.listen(4000, () => {
46 console.log('GraphQL server running at http://localhost:4000/graphql');
47});3. How Does the Flow Work?
Let’s visualize the HTTP request-response flow for this endpoint with a mermaid diagram:
sequenceDiagram
client->>server: POST /graphql {query, variables}
server->>server: Parse body JSON
server->>server: graphql({schema, query, rootValue})
server->>server: Execute resolver
server->>client: HTTP 200 {data, errors}
In broad strokes:
- The client sends a POST request to the
/graphqlendpoint. - The server reads the query from the body (JSON).
- The query is executed using the
graphqlfunction. - The result (
data, and/orerrors) is sent back to the client.
4. Simulating a Request
To try out this endpoint, use cURL or a GraphQL client like Insomnia/Postman.
1curl -X POST http://localhost:4000/graphql \
2 -H "Content-Type: application/json" \
3 -d '{"query": "{ hello }"}'The expected response:
1{
2 "data": {
3 "hello": "Hello, world!"
4 }
5}5. Adding Queries & Parameters
The schema and resolvers can be expanded easily. For example, adding a query with an argument.
1const schema = buildSchema(`
2 type Query {
3 hello(name: String): String
4 }
5`);
6
7const rootValue = {
8 hello: ({ name }) => `Hello, ${name || "world"}!`
9};Request:
1{
2 "query": "{ hello(name: \"Agus\") }"
3}Response:
1{
2 "data": {
3 "hello": "Hello, Agus!"
4 }
5}6. Handling Errors
Suppose the query is invalid:
1{
2 "query": "{ foo }"
3}Output:
1{
2 "errors": [
3 {
4 "message": "Cannot query field \"foo\" on type \"Query\"."
5 }
6 ]
7}7. Table Breakdown: Request & Response Structure
| HTTP Method | Path | Content-Type | Body (JSON) | Response (JSON) |
|---|---|---|---|---|
| POST | /graphql | application/json | { “query”: “…”, “variables”: { … } } | { “data”: {…}, “errors”: […] } |
8. Expanding the Schema Further
GraphQL encourages an expressive schema. Here’s an example of a simple yet slightly more complex schema and resolver.
1const schema = buildSchema(`
2 type User {
3 id: ID
4 name: String
5 age: Int
6 }
7 type Query {
8 user(id: ID!): User
9 users: [User]
10 }
11`);
12
13const users = [
14 { id: "1", name: "Agus", age: 30 },
15 { id: "2", name: "Budi", age: 25 }
16];
17
18const rootValue = {
19 user: ({ id }) => users.find(u => u.id === id),
20 users: () => users
21};Query:
1{
2 "query": "{ users { name, age } }"
3}Response:
1{
2 "data": {
3 "users": [
4 { "name": "Agus", "age": 30 },
5 { "name": "Budi", "age": 25 }
6 ]
7 }
8}9. Production Tips & Conclusion
This basic HTTP server is well suited for prototyping, POCs, or internal tools. For a production deployment, at a minimum you should add:
- Payload and body-size validation (security)
- Logging and error tracking
- CORS support if cross-origin access is needed
- Rate limiting if it’s publicly accessible
That said, the core logic doesn’t change much: accept query, execute, respond. You can embed this code in a service/worker without much overhead.
💡 Conclusion
Without a large framework, we can build a lightweight, easy-to-understand GraphQL HTTP server. It starts from simple HTTP request handling, parsing, query execution, all the way to returning a response. The key takeaway is to understand the fundamental GraphQL flow, not just to rely on ready-made tools.
References
Hope this is helpful, and happy hacking with GraphQL—from engineers, for engineers!