Backend Engineering at Projector

Jeremy Gordon
projector_hq
Published in
4 min readJan 14, 2021

Projector is a modern, web based design platform targeted at folks that value great design, but may not have the skills, time or the budget to invest in traditional high end native desktop design apps.

We chose to implement Projector as a web application, betting on technologies like WebGL and Web Assembly to enable the visual fidelity and performance more typically associated with native desktop applications. Why a web application? Being cloud based, real-time collaborative and cross platform are just as important to us as visual fidelity and performance. In this post, we’ll take a tour through some of the choices we’ve made implementing Projector, and especially Projector’s cloud-based backend.

As a web application, Projector is implemented in two parts: the frontend code that runs in the browser and the backend code running on in the cloud. Projector’s frontend and backend communicate via a mix of REST endpoints and websockets, allowing us to balance simple, robust and familiar REST calls with flexible, fast and realtime websocket messages.

Projector’s backend is Amazon Web Services based, although our use of Kubernetes more or less abstracts a lot of the AWS details from our day to day work. Fun fact: we started Projector on AWS, switched to Google Cloud and back to AWS again early on. Kubernetes for the win!

Alongside Projector’s visual fidelity and fast framerate, its real-time, co-editing collaboration is one of the apps most stand out features. We use an operational transform based approach to allow multiple collaborators to co-edit the same story in real time. Users can see each other’s selection and cursors, edit the same graphical shapes and even edit the same text strings at the same time.

We use a variety of technologies to implement backend features like real-time collaboration. Most of our backend is written in TypeScript, but we also use the Go programming language, a dash of Rust and good old C. To make this possible, Projector is implemented as a service oriented architecture. Our services communicate via gRPC and distributed queues, and are packaged up in containers orchestrated by Kubernetes. We’ve been down the rabbit hole of dozens of microservices and have come back out the other side; consolidating down into a reasonable collection of services more appropriate for a company our size.

One of the great things being a service oriented architecture affords us is the ability to easily pick the right programming language and server hardware for the job at hand. For example, consider the fact that users can export their Projector stories to high resolution videos. The visual fidelity and rendering features of Projector stories require the GPU. From animated effects like water ripples and motion blur, to image filters like halftone or anaglyph, we harness the power of fragment shaders to create visual effects on par with high end native desktop design apps. Despite this we want Projector to remain accessible for users on lower end hardware like Chromebooks, so we render high resolution video exports on the backend.

Projector stories are also complex affairs with embedded media including images, stickers, videos, not to mention a myriad of fonts, vector shapes and animations. To avoid reimplementing the interpretation and rendering of Projector stories, our backend render service is implemented in NodeJS, and shares TypeScript code with our browser based frontend.

Our shared TypeScript rendering code is able to use WebGL in the browser and EGL on the backend to a common purpose: maximize the GPU to create awesome visual fidelity at high frame rates. GPU powered servers aren’t cheap, so we use Kubernetes to schedule our render services to GPU based servers and our other services to more standard commodity server hardware. We use distributed queues to horizontally scale the rendering of Projector stories across a farm of available GPU powered servers, distributing the rendering of a single story to several servers in parallel. Being a service oriented architecture managed by Kubernetes, it’s pretty easy for us to describe the needs of each of our services, and let Kubernetes figure out how to efficiently pack them into the right kind of server hardware, maximizing our performance, not to mention helping to control our costs.

While many parts of Projector’s backend do fancy things like operational transforms and GPU based rendering, much of our backend performs relational database CRUD operations to implement features such as user management, permissions and sharing, comments and email notifications to name a few. Here TypeScript really shines, providing us just the right mix of type safety and flexibility, while giving us access to the epic amount of open source packages available on npm.

Interested in joining us? Get in touch at careers@projector.com.

--

--

Jeremy Gordon
projector_hq

Now: something new! Then: CTO @projector, VP Eng @twitter, game industry refugee (SNES — PS3). Also: coaching HS hoops and soccer. @PositiveCoachUS disciple.