Quick start
Build a small series catalog with a working GraphQL endpoint. By the end, you will be able to create, query, update, and delete a serie, then call the same catalog through an MCP tool.
Download the starter
Download the complete starter and run npm install in the extracted folder. Then configure MongoDB in step 2 and run npm start. The files below are the same files included in the download.
| Before you begin | You will build |
|---|---|
| Node.js 22+, npm, and a transaction-capable MongoDB deployment | A GraphQL endpoint at http://localhost:4000/graphql |
| Docker for the local database recipe, or an existing replica set | A series catalog with embedded seasons and a generated MCP read tool |
GraphQLObjectTypeFields + metadataDescribe data, relationships and application behavior.
connect()Endpoint namesChoose the singular and plural names explicitly.
createSchema()Executable GraphQLModels, inputs and generated resolvers are ready for your server.
1. Create the project
Use a supported Node.js LTS release and npm. The library itself requires Node.js >=18.18.0.
mkdir series-api
cd series-api
npm init -y
npm pkg set type=module
npm pkg set scripts.start="node server.js"
npm install @simtlix/simfinity-js graphql@^16.11.0 mongoose@^8.16.2 graphql-yoga@^5GraphQL and Mongoose are peer dependencies. This guide uses Yoga as the HTTP server; Simfinity generates the schema supplied to it.
2. Start MongoDB
You need a MongoDB deployment that supports transactions. A standalone MongoDB server can accept connections and serve reads, but it cannot execute Simfinity's transactional mutations. Use a replica set or a sharded cluster, as described in the MongoDB transaction requirements.
For local development with Docker, start a single-node replica set:
docker run --name simfinity-mongo -p 127.0.0.1:27017:27017 -d mongo:8 --replSet rs0 --bind_ip_allAfter MongoDB has started, initialize the replica set once:
docker exec simfinity-mongo mongosh --quiet --eval "rs.initiate({_id: 'rs0', members: [{_id: 0, host: 'localhost:27017'}]})"Check that the node is writable before starting the API. Election can take a few seconds; run this again until it prints true:
docker exec simfinity-mongo mongosh --quiet --eval "db.hello().isWritablePrimary"The application below defaults to this local deployment. To use an existing database, set MONGODB_URI to its connection string before starting the server.
Local development configuration
This Docker example binds MongoDB to your machine's loopback interface and does not configure database credentials. Configure authentication and network access for a deployed database. If port 27017 is already occupied, use an available host port and update the application's connection string.
3. Define the schema and server
Keep database initialization and schema registration in schema.js. Both the HTTP server and MCP entry point import this same file. The embedded Season type adds a nested array inside each serie document.
series-api/
package.json
schema.js # database + types + generated schema
server.js # GraphQL HTTP endpoint
mcp.js # generated tool exampleimport { GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
import mongoose from 'mongoose';
import * as simfinity from '@simtlix/simfinity-js';
const SeasonType = new GraphQLObjectType({
name: 'Season',
fields: { number: { type: GraphQLInt } },
});
export const SerieType = new GraphQLObjectType({
name: 'Serie',
description: 'A television serie in the catalog.',
fields: {
id: { type: GraphQLID },
name: {
type: new GraphQLNonNull(GraphQLString),
extensions: { validations: simfinity.validators.stringLength('Name', 2, 120) },
},
year: { type: GraphQLInt },
category: { type: GraphQLString },
seasons: {
type: new GraphQLList(SeasonType),
extensions: { relation: { embedded: true } },
},
},
});
const uri = process.env.MONGODB_URI
|| 'mongodb://127.0.0.1:27017/series?replicaSet=rs0&directConnection=true';
await mongoose.connect(uri);
simfinity.addNoEndpointType(SeasonType);
simfinity.connect(null, SerieType, 'serie', 'series');
export const schema = simfinity.createSchema();
await simfinity.getModel(SerieType).init();import { createServer } from 'node:http';
import { createYoga } from 'graphql-yoga';
import * as simfinity from '@simtlix/simfinity-js';
import { schema } from './schema.js';
const yoga = createYoga({
schema,
plugins: [simfinity.plugins.envelopCountPlugin()],
});
const port = Number(process.env.PORT || 4000);
createServer(yoga).listen(port, '127.0.0.1', () => {
console.log(`GraphQL ready at http://localhost:${port}/graphql`);
});Start the application:
npm startOpen http://localhost:4000/graphql. Yoga provides GraphiQL, an editor with schema discovery and autocomplete. The server setup follows Yoga's Node.js integration.
4. Create your first record
Paste this operation into GraphiQL and run it:
mutation CreateSerie {
addserie(input: {
name: "The Expanse"
year: 2015
category: "Science fiction"
seasons: [{ number: 1 }, { number: 2 }]
}) {
id
name
year
category
seasons { number }
}
}Example response; MongoDB generates a different ID for your record:
{
"data": {
"addserie": {
"id": "507f1f77bcf86cd799439011",
"name": "The Expanse",
"year": 2015,
"category": "Science fiction",
"seasons": [{ "number": 1 }, { "number": 2 }]
}
}
}Keep the returned id for the update and delete examples.
Endpoint names are explicit
connect(null, SerieType, 'serie', 'series') generates addserie, updateserie, and deleteserie. The GraphQL operation name CreateSerie is your own label and can use any naming convention.
5. Query the catalog
query BrowseSeries {
series(
category: { operator: EQ, value: "Science fiction" }
pagination: { page: 1, size: 10, count: true }
sort: { terms: [{ field: "name", order: ASC }] }
) {
id
name
year
}
}The list is returned directly under data.series. With the count plugin enabled, a nonzero total matching count is added as extensions.count. See pagination and counts for current count behavior.
6. Update and delete
Use the ID returned by CreateSerie as the $id variable. Run the update before the delete:
mutation UpdateSerie($id: ID!) {
updateserie(input: { id: $id, category: "Sci-fi" }) {
id
name
category
}
}mutation DeleteSerie($id: ID!) {
deleteserie(id: $id) {
id
name
}
}{
"id": "REPLACE_WITH_THE_CREATED_ID"
}Update inputs include id and the fields you want to change. Deleting returns the deleted record, so you can request its fields in the response.
7. Call the same API through MCP
Create mcp.js alongside the other files, or use the file in the download:
import mongoose from 'mongoose';
import * as simfinity from '@simtlix/simfinity-js';
import { schema } from './schema.js';
try {
const { tools, callTool } = simfinity.generateMCPTools(schema, {
include: ['series'],
limits: { maxPageSize: 100, defaultPagination: { page: 1, size: 10 } },
});
console.log('Generated tools:', tools.map(tool => tool.name));
const result = await callTool('series', {
name: { operator: 'LIKE', value: 'Expanse' },
pagination: { page: 1, size: 10 },
});
console.log(JSON.stringify(result, null, 2));
} finally {
await mongoose.disconnect();
}Run node mcp.js after creating the record. If you ran the delete example, create it again to include it in the tool response. It prints the generated series tool name and its result, then closes its database connection. This calls the tool directly; follow MCP integration to attach a protocol transport or configure permissions.
Where to go next
- Add seasons using relationships.
- Define business rules with validation and controllers.
- Add authorization before exposing protected data.
- Explore the complete Series Sample Project.
Troubleshooting
| Symptom | What to check |
|---|---|
Transaction numbers are only allowed on a replica set member or mongos | Initialize a replica set and use its URI. A standalone server is insufficient for mutations. |
| Database connection fails | Check that MongoDB is running, the URI is correct, and the replica set has a writable primary. |
Cannot query field "addSerie" | Use the exact generated name: addserie. |
| Required input is missing | Supply name when creating and id when updating. GraphiQL shows the generated input types. |
A rejected value produces Unexpected error. | Yoga masks Simfinity validation errors by default. See error handling to expose selected application errors. |
EADDRINUSE on port 4000 | Change the port passed to server.listen() and open that port in your browser. |