After a long week of meetings and shifting priorities, I needed a win—a small, well-scoped feature I could design, implement, and deploy. I chose to focus on the /v1/content/{contentID}/like
endpoint from dittos.io, a social collaboration platform I’ve been steadily modernizing over the past year.
At first glance, it’s a simple “like” toggle. But under the surface, it’s a representative slice of a much larger system.
Background
dittos.io is a custom-built platform for communication and collaboration targeting schools, clubs, and nonprofits. It features newsfeeds, real-time chat, calendar events, community groups, and a permissions model built around membership and shared visibility.
Originally built in PHP using Yii2, it has been progressively ported to Go. Approximately 15% of the API has already been migrated—including authentication, calendars, newsfeeds, and real-time messaging. The next step was introducing the first content endpoint in the new Golang based /v2
namespace. By using /v2
for the Golang implementation, I could focus on a single feature at a time, making the migration process easier to manage and not worry about any breaking changes that may be found as I progress.
The Endpoint
When a user clicks the “like” button on a piece of content, they’re toggling their engagement. It’s a stateless action, but with real access control and structural dependencies:
- Confirm the user is authenticated
- Load the content record
- Load all associated communities the content belongs to
- Validate access:
- Is the content in a public community?
- Is the user subscribed to at least one of the communities?
- Are any of the communities archived (which makes their content inaccessible)?
- Toggle the like:
- If the user already liked it → remove the record
- If not → insert a new like record
- Refresh like metadata
- Return the updated like state and count
The original PHP implementation performed this in a single controller action. Porting it to Go required modularizing each of those concerns.
What I Built
This wasn’t just a port—it was an opportunity to formalize a scalable, maintainable content access layer in the new Go backend. Here’s what I introduced:
- Middleware to automatically load content by ID
- A new
Content
struct with:- Methods to load related communities
- Access control via
HasAccess(userID)
- Archival logic via
IsArchived()
- A content repository to encapsulate all access and mutation logic
- A handler at
/v2/content/{contentID}/like
matching the existing behavior - Integration with the existing user context and session validation built for calendars and newsfeeds
This endpoint represents the first significant interaction feature ported into the new Go stack.
The Result
The new Go-based endpoint is fully compatible with the existing frontend clients (Nuxt and Flutter). Updating a single line in each client to point from /v1/
to /v2/
was all that was required.
Performance saw an immediate benefit. On the same development machine:
- PHP version: 105ms average response time
- Go version: 15ms average response time
The logic is clearer. The architecture is easier to extend. And the performance speaks for itself.
Why It Matters
Porting legacy systems isn’t about rewriting everything at once. It’s about building forward momentum—one endpoint, one system boundary at a time.
This small win set the foundation for the entire content subsystem in Go. And with it, the path to a more modern, scalable, and maintainable future for dittos.io becomes that much clearer.
And the best part? The entire port—including middleware, access control, and new repository logic—took about 90 minutes. When your foundation is solid, progress accelerates.
—
Marc Lewis
Founder, Caffeinated Softworks
https://caffeinatedsoftworks.com