Back to Blog
Lesson 36 of the GraphQL: Your First GraphQL Schema & Server course
August 23, 20263 min read

Connecting to JSON Data: Loading External Files in Node.js

Learn how to move data out of your source code and into persistent JSON files. Master Node.js file I/O to populate your GraphQL server context effectively.


Previously in this course, we explored managing mutation state by updating in-memory arrays. While that works for learning the mechanics of GraphQL, keeping data in your source code is not scalable. In this lesson, we will move our data into external JSON files and learn how to read them directly into our Node.js server.

From Hardcoded Arrays to File I/O

Hardcoded data is volatile—every time you restart your server or make a typo, your state resets. By moving data to a JSON file, you decouple your data layer from your application logic. This is the first step toward working with actual databases like those discussed in defining data schemas in MongoDB with Mongoose.

In Node.js, we interact with the file system using the built-in fs module. To integrate this with our Apollo Server, we need to read the file, parse the JSON string into a JavaScript object, and provide it to our resolvers via the context object.

Loading Data with fs

First, create a file named data.json in your project root:

JSON
[
  { "id": "1", "title": "Understanding GraphQL", "author": "Jane Doe" },
  { "id": "2", "title": "Node.js Fundamentals", "author": "John Smith" }
]

Now, we need to load this into our server. While fs.readFileSync is common for configuration, for dynamic data, we want to ensure we are handling the file path correctly relative to the project root.

JAVASCRIPT
const fs = require(CE9178">'fs');
const path = require(CE9178">'path');

// Resolve the path to our data file
const dataPath = path.join(__dirname, CE9178">'data.json');

// Read and parse the file
const loadData = () => {
  try {
    const rawData = fs.readFileSync(dataPath, CE9178">'utf8');
    return JSON.parse(rawData);
  } catch (error) {
    console.error("Error reading data file:", error);
    return [];
  }
};

const books = loadData();

Injecting into GraphQL Context

Instead of importing books directly into every resolver, we inject it into our Apollo Server context. This makes your application easier to test and prepares you for validating inputs later on.

JAVASCRIPT
const { ApolloServer } = require(CE9178">'apollo-server');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => ({
    books: loadData() // Refresh data on every request
  })
});

By calling loadData() inside the context function, we ensure the server reads the latest version of the file for every incoming GraphQL operation.

Practice Exercise

  1. Create a users.json file in your project directory containing an array of objects with id and username fields.
  2. Update your Apollo Server context function to load this file.
  3. Write a resolver that returns the list of users from the context instead of a hardcoded variable.
  4. Verify the change by querying the list through Apollo Sandbox.

Common Pitfalls

  • Relative Path Errors: Using fs.readFileSync('./data.json') can fail if you run the server from a different directory. Always use path.join(__dirname, 'filename') to ensure the path is absolute and reliable.
  • Performance: Reading a file from disk on every request is acceptable for small local files but will become a bottleneck for large datasets. In production, you would typically use a database or a caching layer, as explored in designing for cache invalidation.
  • Blocking I/O: readFileSync is synchronous and blocks the event loop. While fine for small startup tasks, we will address non-blocking patterns in the next lesson.

FAQ

Why not just use require('./data.json')? Using require caches the file contents. If you modify the JSON file while the server is running, the server won't see the updates. fs.readFileSync ensures you get the fresh state.

Is this safe for production? No. JSON files do not support concurrent writes, ACID transactions, or advanced querying. It is a bridge to learn data modeling before moving to full database solutions.

Recap

We have successfully extracted our data from the server code and placed it into a persistent JSON file. By leveraging the fs module and the GraphQL context, we've created a more modular and realistic API architecture.

Up next: We will tackle the performance limitations of synchronous file reading by moving to Asynchronous Resolvers.

Similar Posts