Before the app could do anything, it needed something to work with. So I needed to build the backend first. I started out setting up the schema, defining the model, with the goal of seeding the database with some actual data. It's one of those tiresome jobs that just needs to be done before anything else, and at this point I was itching to get something visual down, so I wasn't enjoying this at all.
Defining the schema
I'm most used to and am using MongoDB with Mongoose for VISOR, so the first step was defining a schema that reflects the kind of data I want to work with. I decided to start with the Film Simulations because that seemed simpler than a whole Lightroom XMP, it has heaps less data
Here’s the initial schema I wrote:
import mongoose from 'mongoose';
const filmSimSchema = new mongoose.Schema({
name: { type: String, required: true },
type: { type: String, enum: ['Fujifilm', 'Custom'], required: true },
settings: {
grain: String,
dynamicRange: String,
highlight: Number,
shadow: Number,
colour: Number,
sharpness: Number,
noiseReduction: Number,
clarity: Number,
},
description: "String,"
tags: [String],
}, { timestamps: true });
export default mongoose.model('FilmSim', filmSimSchema);
I originally based this on the settings from my Fujifilm X-E2, which came out in 2012. forgetting that obviously, Fujifilm has added loads of new film sims and setting options since then. So later down the line, I had to go back and update the schema to match newer models. I'm not sure why I thought building my entire structure off a 13-year-old camera was a good idea.
Seeding the database
I wrote a simple seed script to populate the database. I passed this to ChatGPT and asked it to fill in some real life film sims in that format:
import mongoose from 'mongoose';
import FilmSim from '../models/FilmSim';
const seedFilmSims = async () => {
await mongoose.connect(process.env.MONGODB_URI);
const sims = [
{
name: 'Classic Chrome',
type: 'Fujifilm',
settings: {
grain: 'Weak + Small',
dynamicRange: 'DR100',
highlight: -1,
shadow: -1,
colour: 0,
sharpness: 0,
noiseReduction: -2,
clarity: 0,
},
description: 'Muted colours, soft contrast — great for street photography.',
tags: ['street', 'muted', 'film-like'],
},
{
name: 'Tri-X 400',
type: 'Custom',
settings: {
grain: 'Strong + Large',
dynamicRange: 'DR200',
highlight: -2,
shadow: +3,
colour: -2,
sharpness: 0,
noiseReduction: -4,
clarity: -1,
},
description: 'High contrast black and white look with punchy shadows.',
tags: ['b&w', 'contrast', 'classic'],
},
];
await FilmSim.insertMany(sims);
console.log('Film sims seeded.');
mongoose.disconnect();
};
seedFilmSims();
To run it:
npm run seed:filmSims
It's pretty simple but means the backend is usable — no blank states, no filler text, no flaky mock layers. Which gives me space to build and start designing the frontend, which was what I was actually excited about.
You've probably notice I had forgot to add sample images, which for a visual application is a massive oversight. I added these manually in MongoDB Compass and updated the schemas.
Visualising it all with MongoDB Compass
I love using MongoDB Compass to visualise the backend. It gives me a clear view of what’s in each collection, how documents are structured, and lets me edit things sneakily if needed — great for fixing weird test cases or just experimenting with values on the fly.
But mainly, when I first started working with a backend, I couldn’t get my head around how you couldn't see the data in a document format, I was creating data, but I couldn't see it anywhere until I called it to the frontend, that was so foreign to me.
I always keep Compass open alongside the app just to sanity-check what’s going on.
Why I seeded instead of mocking
I’ve used mock data in the past, and yeah it does a job — especially early on or for testing isolated components. But in this case, I wanted to test the real data flow right away. Seeding meant I was hitting GraphQL endpoints and querying real MongoDB data from the start. When working on a evolving project like this, its really nice to know that when I update something in the actual DB rather than the mock data, its going to stick.
I'm finding issues all the time with this app, its changing every time I open the codebase, Lightroom or pick up my camera, maybe I should have spent more time on the planning. 🤷♂️
GraphQL
I like working with GraphQL — it's got a nice structure, and lets me define exactly what the frontend needs. But it can also be a bit of a difficult.
One typo in a query, one wrong resolver, one missing field and everything breaks. The app won’t render, the dev tools shouts at you, and it’s not always obvious which part of the pipeline is broken.
Seeding the backend from the get-go meant I was looking at errors a lot. In another project, I built a full REST API that just sent data back and forth. Really, it was kind of easier to understand than GraphQL. When something broke, it was usually obvious — no schema mismatch or cryptic resolver issues, just a straightforward request and response.
I could keep writing about GraphQL and the reasons it’s my choice for VISOR but I think I’ll write another post about this shortly.
Final thought
Seeding wasn’t just about filling in some test data — it was all about building the foundations for the app. I got get real queries, real data, and real confidence that the structure I had built actually worked. It sped up development, and gave the frontend something solid to interact with.
Top comments (1)
Nice write-up! 💗