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.
JAVASCRIPTconst 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.
JAVASCRIPTconst { 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
- Create a
users.jsonfile in your project directory containing an array of objects withidandusernamefields. - Update your Apollo Server
contextfunction to load this file. - Write a resolver that returns the list of users from the context instead of a hardcoded variable.
- 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 usepath.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:
readFileSyncis 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.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

AI Automation & Agentic Workflow Development
Automate the repetitive work eating your time — content pipelines, data workflows, and agentic AI tasks that run themselves.


