Using the GraphCMS Mutation API to import content from CSV

Jamie Barton
Jamie Barton

June 11, 2021

Migrating from any platform to another isn't a straightforward process, the same goes for a Headless CMS. GraphCMS provides the SDK, APIs, and UI to make the process easier to create, and manage your schema and content.

The GraphCMS schema and Content API are completely designed by you. This means any content types, and fields you add will immediately be available via the API. There are a few system content types, and system fields you get out of the box, but you can use as much, or as little as you need.

Before you begin creating any schema, or content, take a look at what content, and content types (referred to as "models" at GraphCMS) you already have, and what they may look like in GraphCMS.

GraphCMS supports over a dozen field types, everything from regular strings, booleans, dates, polymorphic relations (Union Types) to remote field level resolvers that let you fetch content from other APIs.

You'll want to begin by deciding on the models, and fields you need.

Some users migrating prefer to take the time now to normalize their schema in a way that could not be done before, but this can often lead to manual intervention when importing content.

If you don't normalize this now, you can always use String, and JSON fields to represent as much of your data without any modification, however you'll lose some benefits with filtering that data at an API later if fields aren't set to their best field types.

1. Explore your current data and relations

Look at your existing data, and export it to a format such as JSON, or CSV. You can use this export to inspect your existing content types, and field names, as well as any relations between data.

For the purposes of this article, we'll use a very basic CSV of authors:

firstName;lastName
Jamie;Barton
Michael;Lukaszczyk
Daniel;Winter
Jonas;Faber
Bruno;Scheufler
Aien;Saidi
Pablo;Fernandez Busch
Marcos;Pasqualino

2. Create your new schema

You can use the GraphCMS schema editor UI to manage your schema, or use the Management SDK to create your models, fields, enums, relations programmatically.

In the example below, we'll import the @graphcms/management dependency, and create a model, and add some fields.

// migration.js
const { newMigration, FieldType } = require("@graphcms/management");
const migration = newMigration({ endpoint: "...", authToken: "..." });
const author = migration.createModel({
apiId: "Author",
apiIdPlural: "Authors",
displayName: "Author",
});
author.addSimpleField({
apiId: "firstName",
displayName: "First Name",
type: FieldType.String,
});
author.addSimpleField({
apiId: "lastName",
displayName: "Last Name",
type: FieldType.String,
});
migration.run();

You'll need your GraphCMS endpoint, and a Permanent Auth Token with Management API access granted.

Next, you'll need to run this with node. If you'd like to see the result or any errors of the migration, you can opt to run this migration in the foreground by passing true as the first argument to run().

const result = migration.run(foreground);
if (result.errors) {
console.log(result.errors);
} else {
console.log(result.name);
}

Depending on what filename you gave the migration, you can run node migration.js from the command line.

If you introspect your GraphCMS endpoint, or visit the schema editor UI, you'll notice the model "Author" with the fields "First Name", and "Last Name".

There are many other options available using the Management SDK, so it's recommended you read through the documentation, and understand everything that is available before using it.

Now we've some schema, it's time to import content!

3. Import your existing content

Before you can begin creating any content within GraphCMS, you'll want to inspect the auto-generated GraphQL mutations for your models.

Let's take a look at mutations that are added to the GraphCMS GraphQL schema for the new "Author" model:

createAuthor(data: AuthorCreateInput!): Author
updateAuthor(where: AuthorWhereUniqueInput!, data: AuthorUpdateInput!): Author
deleteAuthor(where: AuthorWhereUniqueInput!): Author
upsertAuthor(
where: AuthorWhereUniqueInput!
upsert: AuthorUpsertInput!
): Author
publishAuthor(
where: AuthorWhereUniqueInput!
to: [Stage!]! = [PUBLISHED]
): Author
unpublishAuthor(
where: AuthorWhereUniqueInput!
from: [Stage!]! = [PUBLISHED]
): Author
updateManyAuthorsConnection(
where: AuthorManyWhereInput
data: AuthorUpdateManyInput!
skip: Int
first: Int
last: Int
before: ID
after: ID
): AuthorConnection!
deleteManyAuthorsConnection(
where: AuthorManyWhereInput
skip: Int
first: Int
last: Int
before: ID
after: ID
): AuthorConnection!
publishManyAuthorsConnection(
where: AuthorManyWhereInput
from: Stage = DRAFT
to: [Stage!]! = [PUBLISHED]
skip: Int
first: Int
last: Int
before: ID
after: ID
): AuthorConnection!
unpublishManyAuthorsConnection(
where: AuthorManyWhereInput
stage: Stage = DRAFT
from: [Stage!]! = [PUBLISHED]
skip: Int
first: Int
last: Int
before: ID
after: ID
): AuthorConnection!

You'll want to inspect the types referenced above, but if we take the AuthorCreateInput which we'll need when importing content, you can see we provide some values:

input AuthorCreateInput {
createdAt: DateTime
updatedAt: DateTime
firstName: String
lastName: String
}

Creating an author will return the following Author type:

interface Node {
id: ID!
stage: Stage!
}
type Author implements Node {
stage: Stage!
documentInStages(
stages: [Stage!]! = [PUBLISHED, DRAFT, QA]
includeCurrent: Boolean! = false
inheritLocale: Boolean! = false
): [Author!]!
id: ID!
createdAt: DateTime!
createdBy(locales: [Locale!]): User
updatedAt: DateTime!
updatedBy(locales: [Locale!]): User
publishedAt: DateTime
publishedBy(locales: [Locale!]): User
firstName: String
lastName: String
history(
limit: Int! = 10
skip: Int! = 0
stageOverride: Stage
): [Version!]!
}

Since your data is mostly of a different shape, and structure than it was before, it's quite difficult to create a 1:1 mapping, and import from your existing database to GraphCMS. Because of this, you will want to consider tweaking your current dataset to match your new input types.

Now you're ready to import, you should manage the imports in a queue.

const run = async () => {
const data = csvToJson.getJsonFromCsv("./data.csv");
const queue = new Queue("GraphCMS Import");
await Promise.all(
data.map(async (row) => {
const job = await queue.createJob(row).backoff("fixed", 5000).save();
return job;
})
);
queue.on("job succeeded", (jobId) => console.log(`[SUCCESS]: ${jobId}`));
queue.on("job failed", (jobId, err) =>
console.log(`[FAILED]: ${jobId} (${err})`)
);
await queue.process(async (job) => await createContentEntry(job.data));
};
run();

When each job is processed, it executes the createContentEntry function. This function is what will make a request to GraphCMS to perform a GraphQL mutation, specifically createAuthor.

const createContentEntry = async (variables) => {
const client = new GraphQLClient(process.env.GRAPHCMS_ENDPOINT, {
headers: {
Authorization: `Bearer ${process.env.GRAPHCMS_TOKEN}`,
},
});
const query = gql`
mutation createAuthor($firstName: String, $lastName: String) {
createAuthor(data: { firstName: $firstName, lastName: $lastName }) {
id
}
}
`;
return client.request(query, variables);
};

That's it!

Hopefully, this article was useful for giving you some ideas on how you can manage to import content to GraphCMS.


It's Easy To Get Started

GraphCMS plans are flexibly suited to accommodate your growth. Get started for free, or reach out to our sales team to discuss larger projects with more complex needs