It’s that time again. Our quarterly catch up is here, and I’m about to dish out the hottest goss on all things OpenCraft and Open edX 😉. So, drumroll please…


Top news

Open edX presents Content Tagging!

"Tagging" has been a long-requested feature for managing content in Studio, and now OpenCraft is finally designing and building it for Axim Collaborative! While there are big plans, the first release will be a user-friendly, minimal viable product. Here are some of the things everyone can look forward to:

So, get ready for more organized learning content. Tagging will make it easier for you to find, curate, and build relationships between course content. Your learners will thank you too! Think of courses that are effortlessly searchable and primed for personalization.

Contact us if you’d like to know more about the project.


Open edX community news

  1. The latest Open edX release is here! Palm is packed with improvements big and small! Instructors can now schedule bulk emails, enjoy an improved grading experience, add problems using the new visual editor, and much more.
  2. New to Open edX, or interested in learning more about its background, or how to contribute? The Intro to the Open edX Project & Contributing is for you! Hot off the press, it covers the project’s history, introduces the community, and gives you the lowdown on how to contribute.
  3. The next Open edX meetup: Generative AI & Sustainability Strategies Leveraging the Open edX Platform is scheduled for Thursday, July 20 at 10am (EDT). Learn more about the event and register for free here.​​​​​​​
  4. Axim Collaborative has shared its first ever 2022 Open edX Annual Report. The report takes an in-depth look at the Open edX project and community. What’s more, our OpenCraft designers were involved in the illustration and layout design of the report. What an honor!

Company news

A feature that will be released soon is copying and pasting course content. Talk about saving content authors more time! OpenCraft is very excited to be involved in the UX and development of this project. Ultimately, the feature will allow authors to copy sections, subsections, units and components, and then paste them into different locations within the same course or into a different course. No more duplicate work - whoop!

Keep your eye on the Open edX forum for the official public release of this feature.


Team news

We’re proud to announce that during the first six months of this year, three more OpenCrafters were bestowed the Core Contributor title. We now have 13 Core Contributors!

The Open edX Core Contributor program allows members to participate in defining and deciding the direction of the platform through design, coding, marketing, and more. To become a member you need to embody commitment to the project, exemplary conduct, and high caliber contributions. Well done to the following three team members:

Code Contributors:

UX and UI Contributor


Team member spotlight

Meet our amazing Chief Technology Officer! Braden hails from Vancouver, Canada. When he’s not doing awesome things with code, you’ll catch him hiking the glorious mountains right on his doorstep. 

Braden started his journey at OpenCraft in 2014. But his journey in open source started with his first contribution at age 14! He loves being a part of the Open edX community, and the community loves him! He was made a Core Contributor in 2020. Projects that have received a lot of his attention are things related to XBlocks and how they're stored, including Modular Learning and Content Libraries. In fact, he’s super happy about being involved with the Modular Learning efforts where he’s building functionality that he and the community have wanted for a long time. Braden particularly loves being involved in architecture discussions. You’ll often catch him being extremely helpful on the Open edX forum.

Outside of OpenCraft and the Open edX community, Braden likes to work on open source JavaScript/TypeScript projects. He’s a big fan of DenoTypeScript, and Next.js. He’s also the co-founder of Neolace and TechNotes, and owner of MacDonald Thoughtstuff Inc. 

As I mentioned, Braden is really active on the Open edX forum, so reach out to him there!

We love delivering top-notch learning management solutions.

We’re an elite team of designers and developers, who love creating quality learning management solutions. Let’s chat about your latest project.

Photo credits: Unsplash

This year, the Open edX Conference will be held from March 28 – 31 in Cambridge, Massachusetts. The conference schedule is jam-packed with awesome tutorials, talks, and workshops.

You will hear from three of our coding wizards. Check out their talks below:

Building Collaborative Classes - Borrowing From Open Source Practices

by Xavier Antoviaque 

Would you like to involve your learners in contributing, building, and improving your courses? 

Xavier is presenting recognized best-practices that have made open source communities successful. With decades of field-tested approaches in building collaborative online projects, open-source communities can provide a useful perspective to educators looking to develop a different kind of engagement with their students.

Xavier will be speaking on March 29, 2023.

About Xavier

Xavier is an Open edX Technical Oversight Committee member and contributor. He’s also active in several community working groups. And he was one the very first outside contributors to the Open edX codebase!

Xavier founded OpenCraft when he noticed there was a huge demand for instance customization and feature development on edX. To take on this challenge, he rounded up some of the best developers in the world!

OpenCraft is now one of the largest contributors to the Open edX code base (outside of edX, of course). We provide design, development, and hosting services for clients like Harvard, MIT, edX/2U, and Esme.

Xavier loves all things open source and has been a tenured contributor of free software communities; he co-created the free software game Card Stories, initiated the Ryzom.org campaign, and is a former board member of April, the primary free software advocacy organization in France.

Xavier is really active on the Open edX discussion forum. So reach out to him there!

Product Development in the Open: From Visibility to Collaboration

by Braden MacDonald, and tCRIL’s Jenna Makowski and Dave Ormsbee

tCRIL and OpenCraft led Product Discovery work on the Modular Learning Initiative. This initiative makes authoring more flexible by ensuring all parts of the course are independent, composable, and reusable. The teams adopted an “open first” philosophy, making product development a transparent process. Braden, Jenna, and Dave will discuss how community input shaped the project’s direction. They’ll also discuss what they learned, how they can improve community input channels, and ways for those that are interested to stay in the loop.

Braden will be speaking on March 29, 2023.

About Braden

Braden MacDonald is the CTO of OpenCraft, a Core Contributor to the Open edX project, and an active full stack developer. Braden has led the development of several major features of the Open edX platform, as well as many projects in the broader community ecosystem. He is the official maintainer of projects like Blockstore and Tutor’s ARM64 plugin, as well as several open-source projects outside of Open edX. When he’s not coding, Braden enjoys answering questions on the community forums.

Navigate your Data Lake with OARS

by Jillian Vogel, and tCRIL’s Brian Mesick

What are your wildest dreams for data analytics on the Open edX platform? Jill and Brian want to make those dreams come true.

In this talk, find out how far the Open Analytics Reference System (OARS) for the Open edX platform has come, and help guide where it's going. Jill and Brian want to hear from educators, administrators, operators, developers, and anyone else who wants to know more about how people are interacting with educational content.

Jill will be speaking on March 30, 2023.

About Jill

Jillian Vogel is a senior open-source developer and DevOps at OpenCraft. She’s an Open edX core contributor, and started contributing to the platform and analytics system in 2016. Jill loves working at OpenCraft because of our values, openness, quality, commitment, and empathy. She’s a firm believer in learning to code. She finds that even if someone doesn’t stay in tech, code is a tool that can be applied to any field, be it science, engineering, arts, or politics.

Catch Jill’s recent interview with LabXchange here.

What is the Open edX Conference?

The Open edX Conference is an annual 4-day event where the Open edX community and experts in ed-tech meet to discuss the latest industry research, technologies to enhance teaching and learning, and any technical advances in the Open edX platform.

We love delivering top-notch learning management solutions.

We’re an elite team of designers and developers, who love creating quality learning management solutions. Let’s chat about your latest project.

Photo credits: Unsplash

This article was written by Kshitij Sobti, senior open source developer at OpenCraft.

OpenCraft has been interested in working on improvements to the discussions infrastructure in Open edX for quite some time now, and created a proposal for creating a Discourse plugin for Open edX and making Discussions a pluggable component back in 2019.

Since then, we got the opportunity to work on a major blended development project with edX (now 2U) on bringing some of those improvements to fruition.

There have been major changes to the way discussions work in the platform at multiple levels. From foundational changes that allow selecting alternative discussion providers, and a revamped mechanism for configuring discussions, to new discussions UI that continues the trend of moving over the platform interface to React-based MFEs.

We're happy we got to work on this pivotal functionality of the platform and hope to keep the momentum going in making improvements in this area.

Support for Alternative Discussion Providers

One of the key aspects of the work we've done as part of blended projects BD-3 and BD-38 has been to allow users to select alternative discussion providers.

Parts of this functionality have been landing in the platform since Koa, but from Lilac onwards it has been possible to use the new Course Authoring MFE to do so using a much simpler UI.

With the Course Authoring MFE, the discussion tool used for a course is now something that can be selected from a UI rather than just being an inbuilt default.  The Open edX platform will come with out-of-the-box support for Piazza, YellowDig, InScribe, and Ed Discussion. These providers support LTI (Learning Tools Interoperability) 1.1, which is a standard that allows learning tools to communicate and interoperate. In this case, we use it to allow seamless logging in the discussion tool without requiring the user to log in to another site.

With a simple wizard UI it's possible to select the inbuilt forums or a third-party discussion provider, and configure credentials for the tool. Once configured, the discussion tool will appear embedded in the Discussion tab, replacing the standard Open edX forum interface.

A new UI in the Course Authoring MFE allows you to select a Discussion tool

We developed a newly added LTI infrastructure that also makes it possible to have other LTI-based course tabs, which can embed any kind of tool. However, this aspect is currently not easy to access without importing a specially edited course structure outside of studio. We hope to be able to develop this further in a future blended project. 

While the new UI simplifies the use of supported providers, it is also possible to manually configure a different discussion provider that isn’t supported yet by editing the configuration in the admin panel. Instructions for how to do this are at the end of this article.

Adding a new provider to the UI isn’t possible without some development effort. It currently requires some small modifications to the platform and UI. This is another aspect that we’d love to expand on in the future by making it pluggable or customisable.

One provider we'd particularly love to add better support for is Discourse. Discourse is a very popular discussion forum software that is open source, well known, modern and actively developed. Currently, using it requires installing plugins on the Discourse side as well. It also supports a more modern version of LTI (v1.3) that is supported by the platform, which is something OpenCraft worked on as part of Blended Project BD-02 but isn’t supported for discussions just yet. 

A New UI to Configure Discussions

As you may have seen from the above screenshots, there is now a UI to switch between discussion providers. This isn't limited to just the LTI-based providers; there is now a new configuration UI for the internal forums as well.

The Course Authoring MFE also allows configuring a number of discussion settings from a single place

This new UI will allow you to configure some discussion options that were previously only available to edit through advanced course settings. It consolidates most discussions-related settings into one place. This includes the ability to enable anonymous posts, add general discussion topics, configure discussion cohorts division, and blackout dates.

This new interface lives in the Course Authoring MFE, which now also adds a UI to configure other aspects of a course, such as configuring teams, and enabling/disabling the progress tab, notes feature, calculator tool, and course wiki.

Applications and tools that previously required fiddling with advanced course settings and now be set up in a few clicks using the Course Authoring MFE

New Mechanism for Configuring Discussions

The UI is not the only thing that's new. The whole mechanism for configuring discussions has also been revamped.

When authoring course content, you no longer need to create a discussion block and manually set a category and subcategory. With the new mechanism, you simply tick a checkbox for a Unit, and it will automatically be associated with a topic with the same name as the unit, and the discussions UI will show up for that Unit. By default, all Units will automatically be discussable unless they are in graded, or exam subsections.

There is no need for manually setting up a Discussions XBlock anymore

For units in graded subsections, discussions can be enabled using  a new configuration toggle. 

A migration path from the existing XBlock-based mechanism to the new mechanism is still in development. There are certain complex discussion setups, however, that are no longer supported with this new mechanism. 

A couple of scenarios that are no longer supported are:

New Discussions MFE

There is a brand-new Discussions MFE that is intended to replace the existing forum UI hosted by edx-platform. This new UI refresh modernizes the forum UI, making it easier to explore and navigate between topics. While it is still not officially released, it is possible to test and preview it in the latest Nutmeg release. 

This new UI is entirely API-driven, allowing quick browsing, sorting and filtering through topics, and posts. The MFE can be deployed to a subfolder or subdomain and independently accessed for any course that utilizes discussions. User avatars are now visible next to all user content, making conversations easier to follow.

A new UI has been added to manage learners in a course

There is an entirely new learners section that allows you to see learners in the course, and see their activity on the course. Moderators have the ability to sort learners based on most content reported/flagged for abuse, to make it easy to quickly spot users with most reported activity.

In-Context discussions can now be viewed in a sidebar next to units. The sidebar can be shown or hidden using an icon near the top right that can show different sidebars.

The in-context experience for discussions has also been updated. Rather than appearing in an XBlock in the course flow, discussions can now be viewed within a sidebar next to the course content.

New Discussions Features

The Open edX discussion forum functionality is the only backend feature that is written in Ruby as opposed to Python. While this remains the case, the Ruby forum component (cs_comments_service) has seen a lot of changes recently, with major new features.

The forum now tracks user activity, making it easier to track the level of contribution from different users. The learner section, for instance, will show users that have the highest levels of engagement first.

Edit reasons allow a moderator to clarify the reason for editing a post or comment
Post close reasons allow moderators to clarify the reason for closing a post

For moderation, there is now the ability to provide a reason when performing moderation actions, such as editing content or closing a post. When editing content or closing a post, moderators will see a UI to select a reason for performing that action. For instance, a moderator can select that a post is being closed since it's off-topic, or is a duplicate. Reasons for editing posts or comments, include corrections for grammar and spelling, or removing Personally Identifiable Information (PII), quiz/exam answers, etc. These reasons can be customized by Open edX admins during deployment through Django settings.

How to try out these new Features

If these recent changes seem exciting, you can check out the new discussions configuration experience using the Course Authoring MFE and see the new Discussions UI in action using the Discussions MFE. These MFEs can be set up like any other MFE. 

Using the Course Authoring MFE

If you’ve set up the new Course Authoring MFE, you can simply access it by visiting the following URL for any course: 

[MFE_URL]/course/[course_id]/pages-and-resources/

For instance, on the devstack, for the demo course you’d visit: 

http://localhost:2001/course/course-v1:edX+DemoX+Demo_Course/pages-and-resources/

To have better integration of this MFE with Studio, you need to add the discussions.pages_and_resources_mfe waffle flag to a course or globally. This will add a link to “Pages and Resources” under the “Content” menu in Studio. 

Previewing the new Discussions MFE

Once you’ve set up the Discussions MFE, you can visit the following URL for any course that uses discussions:

    [MFE_URL]/[course_id]/

For instance, on the devstack, for the demo course, you’d visit:

http://localhost:2002/course-v1:OC+NP+Test/

The following waffle flags are available to tweak this new experience:

Preview the new Discussion Configuration Experience

Previewing the new Discussions configuration experience is currently a little more complex, since the migration process for that is still being worked on. This area of the platform is in active development, and as such the process is bound to change. While this feature can be tested in Nutmeg, we’d recommend testing it in a sandbox or development environment running the latest Open edX code. 

However, you can test it out by with the following steps:

If you only saw one provider called “edX” it’s probably because your version doesn’t include the most recent changes. In that case, you may need to manually add a discussion configuration for your course. 

You will now be able to enable/disable discussion for individual units in your course without needing to add an XBlock.

This new experience is still actively being worked on and simplified, so expect this process to get a lot smoother over time, and the configuration to get further streamlined.

Use an Unsupported LTI Discussion Tool

While Piazza, YellowDig, InScribe, and Ed Discussion are available to set up using a simple UI Wizard. The process for configuring an unsupported provider isn’t very complex either. It just needs access to the admin panel. 

Since Lilac, the Open edX platform has had the ability to create a discussion configuration via thevia that the admin panel that will allow using an LTI tool instead of the internal forums. For this you need to visit the discussion configuration page in the LMS’s admin page at [LMS]/admin/discussions/discussionsconfiguration/

Once there, here is what you can do to set up your custom discussion provider:

The Discussion tab on this course should now embed the LTI tool. 

Note that if your provider needs access to a user’s username or email to work, you will need to explicitly enable sharing such PII with the provider using the following additional steps:

{
    "pii_share_username": true,
    "pii_share_email": true
}

Photo by Volodymyr Hryshchenko on Unsplash

A few months back, I wrote a blog post on how to build your own Micro Front End for the Open edX platform. More than half of the post's content was centered around Redux. Redux is complicated. It's frustrating. In fact, most developers I know would rather forget it even exists. However, it's hard to let go of the advantages that Redux gives us. What if we could get all of its advantages, but have a much simpler interface? Enter the Eye of Providence State Management Library, or Providence for short. It gives you better state management for React without the hassle.

Why Use Libraries like Redux in the First Place?

If you've spent any amount of time developing with React, you've probably built something where you need to share state between multiple components at once. There are a few ways to handle this, but the traditional means is to built a Redux store. A Redux store allows you to store all of your state in one place.

Not only that, but it also allows you to replay the entire history of your state using debugging tools. You can rewind and fast-forward to see every way your data has changed since your app initialized! That's pretty neat.

It's exciting to have such power at your fingertips-- but developers are quickly hit with reality. Just getting Redux to work in the most minimal case requires an enormous amount of code, and it's mind-bending to wrap your head around. It's almost as if developers weren't meant to work directly with it.

Using Redux as a Storage Driver

A picture of a hard drive, used to allude to how Providence uses Redux to make better state management for React
Image courtesy Denny Müller via Unsplash

That's because they weren't. The biggest mistake most teams make with Redux is treating it as a state management library. It makes sense-- Redux is sold as the prescribed state management library for React-based projects. However, using Redux for state management is the wrong level of abstraction. Redux's verbosity is needed to provide its guarantees. However, what is verbose can be automated.

Since Redux has to support virtually every state management need you can think of, it has to be very open ended. However, most of what we need to manage on the frontend boils down to:

Providence handles these cases with ease, and lets you focus on development instead of fighting with Redux. Since it's built on top of Redux, you can still use the full power of its features when you need them.

How Providence Provides Better State Management for React

Providence provides better state management for React by autogenerating Redux store modules. It works with Microsoft's Redux Dynamic Modules library to add and remove sections of the state as components need them. Take, for example, this single line of Providence code:

const controller = useSingle('product', {endpoint: 'https://example.com/api/products/x/'})

What does this line get you?

An array is similar:

controller = useList('currentItem', {endpoint: 'https://example.com/api/endpoint/'})

With this array (called a 'List' in Providence parlance), you receive:

A form is a bit more complicated, but not by much:

controller = useForm('survey', {
  endpoint: 'https://example.com/api/survey/',
  fields: {
    age: {value: 20},
    email: {value: '', validators: [{name: 'email'}]},
    name: {value: ''},
  }
})

With this form, you get:

See Providence in Action

A screenshot from Listaflow
Listaflow is an in-development web application that uses Providence to get better state management for React

It may be difficult to wrap your head around the level of magic Providence provides. The best way to get a hold of it is to check out the demo, and then to read its code.

Providence comes with in-depth documentation, and we're currently using it to build a new project, Listaflow, which is open source, and currently being tested in limited beta for the Open edX Core Contributor sprint check ins.

Here's a recap of our May 2022 newsletter:

[Lisbon Conference + Retreat Recap] The OpenCraft team flew to Lisbon, Portugal to attend a team retreat and participate in the 2022 Open edX Conference. All OpenCraft team members work remotely —  the Conference is always a great occasion for us to meet in person, to work together, and cram in a few team meals and fun activities. Our time at the Conference was preceded by a week-long team retreat around Lisbon, Portugal, where we spent quality time together! Read our blog post for a summary of our trip.

[Conference Session Recordings + Feedback] After a very successful 2022 Open edX Conference in Lisbon, all session recordings are available on Open edX’s Youtube channel — go watch them, and don't hesitate to share any feedback or questions. 

Speaking of feedback, two items:

[New app: Listaflow] OpenCraft is building an open source web application, called Listaflow, used for tracking recurring business practices and checklists. The tool was first designed to answer our team's internal asynchronous planning needs, but we're hoping to receive community feedback, and make the tool useful for as many organizations as possible! As a test run, we'll be using Listaflow to run checklists for Core Contributor Sprints. You can take a look at the public code repo, and see the most recent design prototypes

Your feedback and questions are welcome!

[Technical Oversight Committee AMA] Ed Zarecor, Vice President of Engineering at tCRIL, created an "Ask Me Anything" (AMA) public forum thread, where you can ask questions to members of the Technical Oversight Committee (TOC). As a reminder, you can read more about the TOC's role and mission in their foundational charter

-> Review the thread, or ask a question!

This blog post was written by team member Fox Piacenti.

Travel’s been rare over the last year. Recently, our CEO Xavier and I flew into Cambridge and sat down with several clients and community members. It was the first time in a long time. Over the course of two weeks, each order of coffee came with a splash of the same question. Everyone wanted to know: what does the 2U edX acquisition mean for the Open edX community?

It’s a difficult question to answer— until the close of the deal was announced publicly, the details of the acquisition were hidden. When we met with our clients, they were still up in the air. Now we have a few more details. The things we know so far give us reason to be hopeful.

How Can a For-Profit Entity Acquire a Non-profit like edX?

Image of team members reviewing and signing a contract
Image by Romain Dancre on Unsplash

If you’re like me, you wondered how a for-profit acquires a non-profit company. When a for-profit company buys another one, they pay the shareholders in exchange for their stake in the company. With an $800 million price tag — who gets all that money?

Nonprofits don’t have shareholders. At least, not in the traditional sense. Anant Agarwal, founder of edX and now Chief Open Education Officer at 2U, told us he “could not so much as buy a bicycle” with that money. When a for-profit company buys a non-profit company, the for-profit gets some or all of the assets of the non-profit. In this case, it included things like the contracts edX has with different institutions and the employment of edX’s current staff.

In exchange, the money from the transaction directly funds the nonprofit, reorganized, that will continue under the leadership of edX founders Harvard and MIT. This ensures that the money is not used to enrich specific individuals, but is put toward a purpose in line with the organization’s original goals.

In the case of the 2U edX acquisition, the non-profit will be in charge of the Open edX platform (distinct from the edX brand— as has always been the case). They also get ownership over the code and associated repositories. They can use the income to pay off any debts and hire team members to oversee the project, as well as fund other projects related to online education.

What Will Be the Fate of the Open edX Platform?

A coding workstation set up for development
Image courtesy Caspar Camille on Unsplash

We don’t know the full impact. No one knows the answer to many questions about the non-profit. A common concern we’ve heard about the 2U edX acquisition is this: Will the Open edX project now be in the hands of a for-profit company with interests at odds with the community?

In our opinion, no. For one, the non-profit will be in charge of the platform going forward. In fact, this change helps resolve a long-standing conflict of interest where edX had to choose between investing time and resources into edX.org or the Open edX community. To understand why, it’s important to know how edX currently operates.

While almost all content on edX is available for free, edX offers learners the opportunity to pay for a certificate of achievement at the end of a course or program to demonstrate the knowledge they have gained from the content. The courses and programs are provided by Major institutions, along with the guidance of their instructors, through edX.org.

edX open sourced their platform early on and our CEO, Xavier, was the first to open a pull request— on the very day the platform was opened! We’ve been key players in the Open edX community ever since. While edX has been engaged in the community they’ve built around the platform, there has also been some friction.

How the 2U edX Acquisition Could Solve Long-Standing Collaboration Issues

Arms locking together collaboratively, symbolizing the collaborative nature of the 2U edX acquisition
Image courtesy Krakenimages on Unsplash

edX has a model that includes both a free and paid track for courses in order to continue funding their mission to “Increase access to high-quality education for everyone, everywhere”. That means the thrust of their focus has been on achieving this goal. The open source project and its community continued to fuel vibrant contributions to the edX platform and mission. However, despite both edX and the Open edX community wanting to increase focus and investment in the development of Open edX, it could not be the first priority for the organization at the time. Even if it is ‘non-profit’, it still required revenue to run.

As a result, edX focused the platform on its specific use case. The non-profit could change this. With the $800 million price tag of the 2U edX acquisition, the nonprofit will have the resources to last for a long time, perhaps indefinitely, without needing to worry about funding.

With a dedicated team that has a major goal of pushing the platform forward, the entire community stands to gain. This team will not have the same pressures to prioritize edX-specific goals for the codebase over other teams. We still expect 2U to have major (perhaps the largest) influence over the codebase. After all, they will be the biggest users and contributors. But this layer of separation will put organizations from the rest of the community on more even footing.

Our Hopes Going Forward

A pathway through a field between mountains
Image courtesy Lili Popper from Unsplash

As a member of OpenCraft, I’ve enjoyed working on the Open edX platform. edX’s work has allowed us to build a thriving business making Open Source software— a job many developers long to have. The signs I’ve seen talking with edX team members, and members of the community at large, make me hopeful.  The Open edX project is about to get a large boost from the arrival of a well-funded non-profit focused on the project and the community, as well as a new, large contributor with 2U. We gladly welcome both in the growing Open edX community.

This article was written by team member Fox Piacenti

The Open edX platform is a large open source project that has been around close to a decade. The web changed in the years from its initial release. In particular, frontend technology advanced to the point where dedicated applications can be built in JavaScript. To keep pace, the community has adopted the use of "Micro Front Ends" (MFEs) to replace the aging interfaces of the platform. With this technology, teams build custom interfaces for the Open edX platform. We've covered a couple of these MFEs in previous articles, like Publisher and the Editable Gradebook. Itching to make your own custom experience? We'll teach you how to build your own Micro Front End.

Getting Started Creating Your own Micro Front End

If you're used to working with JQuery and Django templates, you may find the list of new technologies daunting. Building frontends in React is a radical departure from the old way of building user interfaces for the platform. Thankfully, a prebuilt template exists to help you get started. Visit the template page and hit the 'Use this template' button to create your own copy of the template repository on GitHub. Some basic familiarity with the platform, the command line, and JavaScript is required.

We will be going over several technologies quickly on a surface level-- you'll need to study further to grasp everything. That said, this should help you set up an MFE of your very own and get you pointed in the right direction.

Once you've created your copy of the repository, it's time to clone it. I've created a new repository called demo-mfe, so I'll clone it this way:

git clone https://github.com/open-craft/demo-mfe.git

Open up the resulting code in your favorite IDE or text editor. We'll start by following the instructions in the template README. First, we'll replace all references to the template repository with references to our own. So, we find edx/frontend-template-application and replace it with open-craft/demo-mfe. Then we search for the string absent the GitHub username, replacing frontend-template-application with demo-mfe.

One more thing you should do while you're here-- take the example file, README-template-frontend-app.rst and replace the README.rst file with it. Update that file according to the instructions. It will give you a great way to organize basic information about your project so newcomers know what it's for and how to use it. If you need the information in the old README.rst, just refer back to the old template repository, or revisit the initial commit.

Building and Running the MFE

Now that you've done your initial customization, it's time to install and run the dev server so you can begin work:

npm install
npm start

NOTE: If you are getting a syntax error from npm start, you may need to upgrade your version of node.

The prompt will tell you that your application is available at https://localhost:8080/, and it is, but it won't run until you start your devstack. Start up your devstack with the LMS, and then view the page, and you'll see your MFE!

This example page isn't much to look at, but there's a lot here. You'll notice there's a standard header and footer, which handle login and registration (mostly, just pointing to the relevant pages on the LMS.) You can log into the LMS and return to your MFE, and you'll see that the header changes:

In this case we're logged in as the default admin user, edx.

Adding a Feature to Your own Micro Front End

Let's do something simple-- let's fetch the course catalog and see what courses are available on the LMS. Start by creating a new directory under src named catalog, and another one named common. We'll want to create a few files here. We'll talk a bit about each file, but if you want to read an in-depth explanation about the directory structure, you'll want to read this document. Here's the structure we'll be making for catalog:

$ tree catalog
catalog
├── CatalogPage.jsx
├── data
│   ├── api.js
│   ├── selectors.js
│   ├── slice.js
│   └── thunks.js
├── index.scss
└── messages.js

1 directory, 8 files

...and here's the structure we'll make for common:

$ tree common
common
├── constants.js
└── store.js

0 directories, 2 files

The very first file we'll create is common/constants.js, which will define a few constants we'll use later.

// Add new names for new features as you build them. This allows you
// to separate out redux state into named areas
// to avoid putting everything in the root.
export const STORE_NAMES = {
  CATALOG: 'catalog',
};

// Right now we have our built-in home view that we'll keep just
// to have something at / when testing, but you'll want to
// be thoughtful about what URL you want to have all your
// action at. After all, MFEs can be configured to be on the same
// domain as the LMS, and so a web server like NginX can proxy
// the request to your MFE if it matches the route.
//
// The LMS has no route at /catalog/, so we'll use that.
// To learn more about react routing, check out https://reactrouter.com/
export const ROUTES = {
  HOME: '/',
  Catalog: {
    HOME: '/catalog/',
  },
};

Setting up State Management

Redux - A predictable state container for JavaScript apps. | Redux logo
We use Redux for State Management in MFEs.

We'll come back to those constants later. For now, we'll move on to catalog/data/slice.js. This file will contain information defining the data we're using and how the MFE should handle it through Redux. Redux is a state management system. We'll not go too deeply into how Redux works, but one team member describes it this way:

Redux helps you keep different parts of your application isolated by acting as the go-between. For instance if you have a button that can show/hide a sidebar, with Redux you'll dispatch an action, let's call it "SHOW-SIDEBAR". You will write code called a reducer which will accept the original state, and the action, and return the new state that results after that action. Here when it sees the "SHOW-SIDEBAR" action the reducer might change the state to set a variable called "sidebarVisible" to true. Finally, your sidebar component can subscribe to changes to this variable so it can show up if that variable is true.

All of this is handled seamlessly and transparently by Redux, so other than some boilerplate, you're just writing normal functions and accessing normal variables.

-Kshitij Sobti

To make things easier, we'll install the Redux Toolkit library:

npm install -s @reduxjs/toolkit

First, we want to define our initial state:

export const initialCatalogState = () => ({
  fetching: false,
  errors: [],
  courses: [],
});

We make this declaration a function so we can call it both during initialization and during tests. This ensures we're not using an old copy of the structure with leftover data. Next, we need to define our reducers. Reducers are functions that mutate state.

export const baseCatalogReducers = {
  fetchCatalogRequest(state) {
    state.fetching = true;
    state.errors = [];
    state.courses = [];
  },
  fetchCatalogSuccess(state, { payload }) {
    state.fetching = false;
    state.courses = payload.courses;
  },
  fetchCatalogFailure(state, { payload }) {
    state.fetching = false;
    state.errors = payload.errors;
  },
};

Finally, we'll want to create the slice object we'll use to manipulate state:

import { createSlice } from '@reduxjs/toolkit'
import { STORE_NAMES } from "../../common/constants";

...

const slice = createSlice({
  name: STORE_NAMES.CATALOG,
  initialState: initialCatalogState(),
  reducers: baseCatalogReducers,
});

export const catalogReducer = slice.reducer;
export const catalogActions = slice.actions;

With the structure of the data defined, and our slice ready, we can create the application's store. This file will be at common/store.js:

import { configureStore } from '@reduxjs/toolkit';
import { catalogReducer } from '../catalog/data/slice';
import { STORE_NAMES } from './constants';

export default configureStore({
  reducer: {
    [STORE_NAMES.CATALOG]: catalogReducer,
  },
});

There's one more step for state management here, and that requires us to create a 'selector.' A selector is an intermediary function that takes the Redux state, selects the most important parts of it (massaging data if needed) and presents it to our components.

This is a little redundant right now because we have only one feature, but if we build more than one, it'll be useful not to have the state of all the other features sent to the one we're working on. Selectors are especially helpful when the state's structure changes elsewhere. In such cases, you only need to update your selector and all the components can stay the same.

We'll create the file at catalog/data/selectors.js:

import { STORE_NAMES } from '../../common/constants';

export default (state) => state[STORE_NAMES.CATALOG];

Creating API Callers and Our 'Thunk'

In order to get what courses are available, we need to write a function to pull that information from the LMS's API. We put this in catalog/data/api.js:

import { ensureConfig, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

ensureConfig(['LMS_BASE_URL'], 'course API service');

export const getCourses = async () => {
  const client = getAuthenticatedHttpClient();
  const baseUrl = getConfig().LMS_BASE_URL;
  const response = await client.get(`${baseUrl}/api/courses/v1/courses/`);
  // This data is actually paginated. The results object contains
  // the first page. For simplicity's sake, we're going to ignore
  // pagination and just use the first page.
  return response.data.results;
};

export default { getCourses };

The helper functions here make this easy-- getAuthenticatedHttpClient gets an HTTP client that's authenticated to the current user (remember the login button from earlier?) ensureConfig, meanwhile, makes certain that the MFE's configuration has an entry for the LMS. If you look in the .env.development file in the root of the project, you'll see this setting is set.

Now that we have our API call, we need to create our 'thunk' that manages both the call and the change to the state. This is what goes in catalog/data/thunks.js:

import * as api from './api';
import { catalogActions as actions } from './slice';

export const fetchCourses = () => async (dispatch) => {
  try {
    dispatch(actions.fetchCatalogRequest({}));
    const courses = await api.getCourses();
    dispatch(actions.fetchCatalogSuccess({ courses }));
  } catch (err) {
    dispatch(actions.fetchCatalogFailure({ errors: [String(err)] }));
  }
};

export default { fetchCourses };

Note that we send all changes to the state through the dispatch function using the reducer functions we created earlier.

While we could just write the thunk to contain the API call, we use a separate file for the API calling functions because several thunks might call to the endpoints for different reasons.

Building Your Micro Front End's Page Component

React Logo Icon
React components power Micro Front Ends

Those last few files were a bit dry. Now it's time to build the components. React components handle the running and display of your Micro Front End. We will configure these components to use internationalization and the accessibility component library, Paragon.

We need to add one more special file before writing the components themselves. This is catalog/messages.js, which will contain all the translatable strings we'll use later in the components.

import { defineMessages } from '@edx/frontend-platform/i18n';

export default defineMessages({
  catalogHeading: {
    id: 'catalogHeading',
    defaultMessage: 'Course Catalog',
    description: 'The page heading for the catalog page.',
  },
  catalogLoading: {
    id: 'catalogCourseLoading',
    defaultMessage: 'Loading...',
    description: 'Loading message when fetching the courses.',
  },
  catalogCourseView: {
    id: 'catalogCourseView',
    defaultMessage: 'View Course',
    description: 'Label for the button that brings the user to the course about page.',
  },
  catalogCourseBannerAlt: {
    id: 'catalogCourseBannerAlt',
    defaultMessage: 'Showcase image for {courseName}',
    description: 'Alt text for course banner images.',
  },
});

OK. Now we're finally ready to write the component itself. We'll place it in src/catalog/CatalogPage.jsx. JSX is an extension of JavaScript that allows you to drop down to an HTML-like syntax for writing template code used by React. It uses syntactic sugar compile this:

<h1 className="title">"Hello World</h1>

...into this:

React.createElement("h1",{className:"title"}, "Hello World")

Anyhow, here's our component code:

import {
  Alert, Card, Col, Container, Row,
} from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getConfig } from '@edx/frontend-platform';
import { useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import Button from '@edx/paragon/dist/Button';
import { fetchCourses } from './data/thunks';
import selectCourses from './data/selectors';
import messages from './messages';

const buildLmsUrl = (absoluteUrl) => `${getConfig().LMS_BASE_URL}${absoluteUrl}`;
const buildCourseURL = (courseKey) => buildLmsUrl(`/courses/${courseKey}/about`);

export const CatalogPageBase = ({ intl }) => {
  // These 'use' functions are React hooks. Hooks let you hook into
  // parts of react like the state management system. You can read
  // more about them here: https://reactjs.org/docs/hooks-overview.html
  const dispatch = useDispatch();
  const { courses, errors, fetching } = useSelector(selectCourses);
  // By providing no dependencies in the second
  // argument, we signal that this hook should be run when the
  // component is mounted.
  useEffect(() => {
    dispatch(fetchCourses());
  }, []);
  return (
    <Container>
      <Row>
        <Col xs={12}>
          <h1>{intl.formatMessage(messages.catalogHeading)}</h1>
        </Col>
        <Col xs={12}>
          {errors.map((error) => <Alert variant="danger" key={error}>{error}</Alert>)}
        </Col>
      </Row>
      {(fetching && (
        <Row>
          <Col className="text-center">{intl.formatMessage(messages.catalogLoading)}</Col>
        </Row>
      )) || (
        <Row>
          {
            // We can get insight into the structure of courses at this
            // endpoint by checking our local copy of it here:
            // http://localhost:18000/api/courses/v1/courses/
          }
          {courses.map((course) => (
            <Col xs={6} md={4} lg={3} key={course.id}>
              <Card>
                <Card.Img
                  variant="top"
                  src={buildLmsUrl(course.media.course_image.uri)}
                  alt={intl.formatMessage(messages.catalogCourseBannerAlt, { courseName: course.name })}
                />
                <Card.Header>{course.name}</Card.Header>
                <Button variant="primary" href={buildCourseURL(course.id)}>{intl.formatMessage(messages.catalogCourseView)}</Button>
              </Card>
            </Col>
          ))}
        </Row>
      )}
    </Container>
  );
};

Note that this component takes an argument, intl. This is the internationalization helper object that we'll eventually pass to the component to do all of the string translations.

Annotating the Component

Speaking of arguments, we next need to set up the component's propTypes. This helps us define how the component is to be called using a 'soft' type checking system. In development, this will send us messages in the console if we ever call a component without arguments that match the propTypes. We define the propTypes this way:

CatalogPageBase.propTypes = {
  intl: intlShape.isRequired,
};

Because we're using hooks, and this is a simple component, the intl object is the only special object we need to include. However it's worth giving you an example of a more complex shape in case you need it. Here's an example shape for defining an animal:

const AnimalShape = PropTypes.shape({
  media: PropTypes.shape({
    sound: PropTypes.shape({
      uri: PropTypes.string.isRequired,
    }),
    pictures: PropTypes.arrayOf(
      PropTypes.shape({uri: PropTypes.string.isRequired, alt: PropTypes.string.isRequired})).isRequired,
  }),
  legs: PropTypes.number,
  name: PropTypes.string.isRequired,
  move: PropTypes.func.isRequired,
});

This would match an object like this:

{
  media: {
    sound: "https://example.com/woof.wav",
    pictures: [
      {uri: "https://example.com/doggo.jpg", alt: "Picture of a dog in sunglasses."},
      {uri: "https://example.com/doggo2.jpg", alt: "Picture of a dog floating in a pool."},
    ],
  },
  legs: 4,
  name: "Dog",
  move (x, y) => {
      // ...
  },
}

...or like this:

{
  media: {
    pictures: [
       {uri: "https://example.com/sponge.jpg", alt: "Picture of a sponge on a coral reef."},
    ],
  },
  name: "Sponge",
  move (x, y) => {
    // ...
    // slooooowly
    // ...
  },
}

The shape of the intl object is imported at the top of the file from earlier. You can go look at the definition to see precisely how it's constructed-- which properties need to be functions, which ones are strings, which are arrays (and what kind!) React will check all of this for you when running the component. Handy!

Finally, we need to inject the internationalization functionality into the component. It isn't as big of a deal here, but when working with many nested components, if you don't inject intl, you'll have to remember to pass it to each and every sub component. Injecting it this way means we get it passed to the component's arguments for free when we call it later:

export const CatalogPage = injectIntl(CatalogPageBase);

The result is a new component with intl always added!

Putting the Micro Front End Together

Now that we have all the parts, we need to assemble them in the Micro Front End's index. This is all the way back in src/index.jsx. Here's the contents:

import 'core-js/stable';
import 'regenerator-runtime/runtime';

import {
  APP_INIT_ERROR, APP_READY, subscribe, initialize,
} from '@edx/frontend-platform';
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import ReactDOM from 'react-dom';

import Header, { messages as headerMessages } from '@edx/frontend-component-header';
import Footer, { messages as footerMessages } from '@edx/frontend-component-footer';

import { Route, Switch } from 'react-router';
import appMessages from './i18n';
import ExamplePage from './example/ExamplePage';

import './index.scss';
import { ROUTES } from './common/constants';
import { CatalogPage } from './catalog/CatalogPage';
import store from './common/store';

subscribe(APP_READY, () => {
  ReactDOM.render(
    <AppProvider store={store}>
      <Header />
      <main>
        <Switch>
          <Route exact path={ROUTES.HOME} component={ExamplePage} />
          <Route exact path={ROUTES.Catalog.HOME} component={CatalogPage} />
        </Switch>
      </main>
      <Footer />
    </AppProvider>,
    document.getElementById('root'),
  );
});

subscribe(APP_INIT_ERROR, (error) => {
  ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
});

initialize({
  messages: [
    appMessages,
    headerMessages,
    footerMessages,
  ],
});

It may be most instructive to ask git what you've changed here using git diff index.jsx-- we've inserted a few things and taken others away. For one, we've added the router and routing. So, the example component our template came with is available at / and our catalog is available at /catalog/. One more thing we've done is pull in the Redux store and add it into the AppContext. The selector and the reducers to pass around the current state and update it.

With all of that together, we should finally be able to see our finished MFE with the new feature at:

https://localhost:8080/catalog/
A screenshot of our new Micro Front End catalog

Congratulations! You've surfed through a ton of new technologies, learned a few new best practices, and created your own MFE! Now you can customize your Open edX platform experience however you like. ?

Get the full code for this Micro Front End here!

Get in touch

We'd love to hear from you! Send us a message and a member of our team will get back to you.
Contact us
Subscribe to our newsletter
edX and Open edX® are trademarks of edX Inc