Update Strapi Beta to Strapi Stable Community Edition
June 15th 2020
At Hash Interactive we’ve been using Strapi since the alpha release years ago. The Headless CMS has made a lot of changes throughout the versions from alpha to beta and now from beta to the stable community release. We have had to do a lot of upgrades and migrations for our Strapi projects and compiled a list of migration steps from an older version of Strapi to the community edition.
The migration guides recommend making the updates in the current working project directory but we have found more success starting with a new strapi project and migrating our custom changes to the new stable project version.
In this tutorial we’ll walk through:
- Updating the Database: Create a new Mongo Database and Migrating the Mongo DB Data
- Updating the Strapi Version: Create a new Strapi Project
- Updating Environment Configuration: Moving from
config/<environment>
to.env
file - Copying API Files to Stable Project
- Updating Model Fields: Changing Strapi Model Field Types
- Updating Routes: Update API Routes Params from
_id
toid
- Updating Media File Relationship: MongoDB Media Relation Changes
- Moving Public Files to new Stable Strapi Project
- Rebuilding the Administration Panel
Create a new Mongo Database and Migrating the Mongo DB Data
In order to update to a new strapi version we’re going to copy over our Mongo Database to a new database installation which will create a database backup and a new database that we can manipulate if needed. First, we’ll use the mongodump utility to dump the current database to an archive file:
mongodump --username <username> --db <database> --host <host> --port <port> --authenticationDatabase <authentication_database> --archive="<archive_file_name>"
We are dumping our database to an archive file. Now we can create a new database for our stable strapi project. We can login to our mongo
shell and we’ll run:
use stable
Now we’ll create a database user:
db.createUser({ user: "YOUR_USER", roles: [{ "role": "dbOwner", "db": "stable" }], pwd: "YOUR_PASSWORD" })
We’re creating a mongo database called stable
and we’re defining a dbOwner
user on our database. Now we can import our database dump archive file by running:
mongorestore --username <username> --authenticationDatabase stable --archive="<archive_file_name>" --nsFrom="<old_databse_name>.*" --nsTo="stable.*"
Include additional options as necessary, such as to specify the uri or host, username, password and authentication database.
Now we should have a new database with all of our old data migrated over to it. We can now create a new Strapi project with our database definition.
Create a new Strapi Project and Moving API Files
We can use the yarn create
utility to create a new strapi project with the most updated 3.x
stable release:
yarn create strapi-app strapi-stable
We can select the manual
configuration option and we’ll select the mongo
database option. When prompt we can input our new mongo database credentials we just created. After we finished install we can review the database configuration file found at strapi-stable/config/database.js
module.exports = ({ env }) => ({
defaultConnection: 'default',
connections: {
default: {
connector: 'mongoose',
settings: {
host: env('DATABASE_HOST', '127.0.0.1'),
srv: env.bool('DATABASE_SRV', false),
port: env.int('DATABASE_PORT', 27017),
database: env('DATABASE_NAME', 'stable'),
username: env('DATABASE_USERNAME', 'YOUR_USERNAME'),
password: env('DATABASE_PASSWORD', 'YOUR_PASSWORD'),
},
options: {
authenticationDatabase: env('AUTHENTICATION_DATABASE', 'stable'),
ssl: env.bool('DATABASE_SSL', false),
},
},
},
});
Updating Environment Configuration
As you can see the new strapi version is using environment variables to define the database options and if those are not defined it falls back to the options that we input when configuring the project. Let’s create a .env
file and define these values so we can use this strategy in our production or staging environment. Create a file in the root called .env
DATABASE_HOST=127.0.0.1
DATABASE_SRV=false
DATABASE_PORT=27017
DATABASE_NAME=stable
DATABASE_USERNAME=<username>
DATABASE_PASSWORD=<password>
AUTHENTICATION_DATABASE=stable
DATABASE_SSL=false
For each deploy or environment you create you can define a new .env
file and the configuration will be picked up for you.
Copying API Files Over to Strapi Stable Project
We can copy all of our API files from the old strapi project to our strapi-stable
folder:
cp -v old/api/* strapi-stable/api/
Now that all of our strapi api files are copied over we can make the required changes to our Models JSON definition.
Updating Model Fields: Changing Strapi Model Field Types
Strapi has changed some field types in the new stable release. If we were previously using the WYSIWYG field we will need to update each Model in our ./api/**/<Model>.settings.json
file to account for the changes.
WYSIWYG fields from text
to richtext
To make sure a Wysiwyg field stays the same when deploying, we introduced the
richtext
type. This type is equivalent to the previoustext
type withwysiwyg
option enabled.
Before:
{
//...
"attributes": {
"name": {
"type": "string"
}
"description": {
"type": "text"
}
}
}
After:
{
//...
"attributes": {
"name": {
"type": "string"
}
"description": {
"type": "richtext"
}
}
}
Likewise, Strapi has made date type changes.
We have introduced new types in the admin panel:
date
,datetime
andtime
. Previously all of those types where saved asdatetime
.You will need to change the type of your fields from
https://strapi.io/documentation/v3.x/migration-guide/migration-guide-beta.17-to-beta.18.html#date-type-changesdate
todatetime
if you don’t want to migrate your data.
Before:
{
//...
"attributes": {
"name": {
"type": "string"
}
"published": {
"type": "date"
}
}
}
After:
{
//...
"attributes": {
"name": {
"type": "string"
}
"published": {
"type": "datetime"
}
}
}
Now that we have our field types corrected we can change our routes configuration for our api endpoints.
Updating Routes: Update API Routes Params
In order to stay database agnostic, strapi decided that the identifier in url params should be named id
instead of _id
.
If your routes configuration still uses something else than id
, you will need to modify all of them as in the following example:
Before: ./api/**/config/routes.json
{
"method": "PUT",
"path": "/assets/:_id",
"handler": "Asset.update",
"config": {
"policies": []
}
}
After: ./api/**/config/routes.json
{
"method": "PUT",
"path": "/assets/:id",
"handler": "Asset.update",
"config": {
"policies": []
}
}
Updating Media File Relationship in MongoDB
In the media library features, We wanted to make sure media would keep their ordering. To implement this in mongo we had to change the way the media relation was built.
https://strapi.io/documentation/v3.x/migration-guide/migration-guide-beta.19-to-beta.20.html#mongodb-media-relation-changes
Previously, theupload_file
collection was the one keeping track of the relations and the entity related to the file had not reference to it. Implementing ordering without changes the relations proved unfeasible. Finally we decided to add the reverse reference in the entities so it would make accessing the files really easy. You will hence need to migrate yourmongo
database to avoid losing references to your files.
If you were using MongoDB and had relationships between Content Types and the Media Library upload_file
we will need to generate some scripts to migrate the relationship.
First create a export.js
file at the root of your project with the following content:
const fs = require('fs');
require('strapi')()
.load()
.then(() => {
const models = {};
Object.keys(strapi.api).forEach(apiName => {
Object.values(strapi.api[apiName].models || {}).forEach(model => {
models[model.globalId] = formatModel(model);
});
});
Object.keys(strapi.plugins).forEach(pluginName => {
Object.values(strapi.plugins[pluginName].models || {}).forEach(model => {
models[model.globalId] = formatModel(model);
});
});
Object.values(strapi.components).forEach(model => {
models[model.globalId] = formatModel(model);
});
fs.writeFileSync('models.json', JSON.stringify(models, null, 2));
process.exit(0);
});
function formatModel(model) {
return {
collection: model.collectionName,
files: Object.keys(model.attributes).reduce((acc, key) => {
const attr = model.attributes[key];
if (attr.model === 'file' && attr.plugin === 'upload') {
acc[key] = 'single';
}
if (attr.collection === 'file' && attr.plugin === 'upload') {
acc[key] = 'multiple';
}
return acc;
}, {}),
};
}
Then we can run the script from the root of your project:
node export.js
This will create a models.json
file at the root of our project directory. We will then need to create a script (outside of the project root) that is called migration.js
in which we will load into our mongo database. Inside migration.js
we can populate the script:
var models = {
// copy models.json file
}
for (var i in models) {
var model = models[i];
var update = {};
var keyCount = 0;
for (var key in model.files) {
keyCount += 1;
update[key] = '';
}
if (keyCount > 0) {
db.getCollection(model.collection).update({}, { $unset: update }, { multi: true });
}
}
var fileCursor = db.getCollection('upload_file').find({});
while (fileCursor.hasNext()) {
var el = fileCursor.next();
el.related.forEach(function(fileRef) {
var model = models[fileRef.kind];
if (!model) {
return;
}
var fieldType = model.files && model.files[fileRef.field];
// stop if the file points to a field the user didn't specify
if (!fieldType) {
return;
}
if (fieldType === 'single') {
db.getCollection(model.collection).updateOne(
{ _id: fileRef.ref },
{ $set: { [fileRef.field]: el._id } }
);
} else if (fieldType === 'multiple') {
db.getCollection(model.collection).updateOne(
{ _id: fileRef.ref },
{ $push: { [fileRef.field]: el._id } }
);
}
});
}
We will need to copy the output from models.json
file to be equal to our models
variable in our migration.js
script such as:
var models = {
ModelName: {
collection: 'collectionName',
files: {
image: 'single',
images: 'multiple',
},
},
};
//... rest of the migration.js script
Now with our migration.js
script defined we can log into our Mongo DB shell and load the script. First, make sure we open a terminal and cd
to the working directory where our migration.js
file is located. Then we can run the command:
mongo
> use stable
> ls()
> load("./migration.js")
Once our migration script is done loading we can delete the files export.js
and models.json
in our root directory.
Moving Public Files to new Stable Strapi Project
Lastly, we’ll need to migrate the files in our public/uploads
directory to our new project. We can easily run the command:
cp old/public/uploads/* strapi-stable/public/uploads/
This will copy all of our Media Files from our old project into our stable strapi project.
Rebuilding the Administration Panel
Before deploying your Strapi application in production we’ll need to rebuild our administration panel.
You can run yarn build --clean
to rebuild your admin panel with the newly installed version of strapi.
Finally restart your server: yarn develop
in development mode or yarn start
in production mode.
Hopefully these are some tips to help you in your migration from Strapi Beta version to the Stable release edition of Strapi. Every migration will be a little different in it’s requirements but these are some common issues we have previously ran into during migrations.