I'm a detail-oriented software engineer with 5 years of experience in fullstack web development. I'm targeting roles involving TypeScript, React,
Ruby and Go with an opportunity to transfer to
North America.
I've integrated Portchain's data platform with terminals from Central
America and Southern Europe using REST APIs. See more...These integrations periodically check for updates from terminals, match
their schedules with Portchain's data and synchronize between the two.
Other
Implemented MVP features as part of a bet which got us a new major
customer.
Played a role in migration from Heroku to Google Cloud Platform to
decrease infrastructure costs.
Maintained a deployer application used to simplify deployments to
various environments on Google Cloud Platform.
Modernized the codebase by introducing Dependabot and updating
dependencies.
I have driven increased product demand by developing a calculator that
helps founders model unpriced funding rounds.See more...The solution involved an RFC-like process to come up with the best solution as this was the first
public page (i.e. not behind auth, obviously excluding a sign in/sign up
flow). At the time we were also in a rebranding transition where the
homepage was already transitioned and the idea was to use the new design
on all public facing pages so the calculator should also follow the new
design. It uses many primitive components and other more complex
components some of which I helped building. Check it out for yourself!
Billing revamp projectwar story
I enabled better sales offering and streamlined customer billing
management by revamping billing in a Ruby on Rails app integration with
Stripe and HubSpot.See more...
This was the longest running project I worked on and wrote around 90% of
it when it comes solely to the changes in the app codebase. First step
was a migration from one Stripe instance to another. The next step involved changing how the app is interacting with
Stripe where previously it was the app recording changes to customer
subscriptions using Stripe's API and now the idea was to make these
changes from the self-service customer portal and update the app data via webhook events from Stripe. It also involved
a change to the pricing packages where there was previously a single package
and additional customer features were enabled on an ad-hoc basis and now
there would be multiple packages with certain pre-enabled features.
Fundraising for early-stage startups
This project helps early-stage startups in fundraising and offering them
inexpensive assistance on tax relief to ensure their success. See more...Services included are round modelling calculator, creating and filing
advance assurance, SEIS & EIS, and templates for ASAs & SAFEs. Major
challenge in this project was a tight deadline that was agreed on few
months in advance and that had to be met which I achieved without
sacrificing quality.
In-app workflows
The in-app workflows I implemented increased number of requests for an
additional paid service from ~5 to 20+ a month increasing revenue in this
area by 300%.
See more...These included a backoffice part, where our colleagues fulfilling the
request were able to manage and update progress on the requests, and the
end customer part, where customers were able to request these services.
Currency exchange rate provider
I provided affordable currency exchange rate solution for 200+ currencies.See more...While the app had a multi-currency support from the time I joined, the
currencies that were supported were those available from European Central Bank which contains 30 currencies. Meanwhile, there are 180 currently circulating currencies recognized by UN and some of those started to appear in the app and it became cumbersome
to deal with them manually.
I used Exchangerates API to get a more comprehensive list of the exchange rates. As we didn't
call the API everytime we needed the exchange rate but instead stored it
in a database (we also needed historical exchange rates in some cases),
new exchange rates were automatically added to the database on a daily
basis.
Automated invalid link checker
Addressed invalid links by automatically checking and reporting them.See more...An external link can change and suddenly you're linking to a
non-existent page. At some point we encountered a few links that were
invalid and instead of just fixing those and moving on with our life I
wrote a script to check external links that I set up to run on GitHub Actions on a weekly basis and send a result
to an #automated-checks Slack channel. In this way, these issues became
more visible and otherwise invalid links that would be in the app for many
months were fixed within a week.This check has been implemented for this blog as well.
Improved modal component
Improved both UX and DX by improving a modal component. See more...The way we were writing most of the modals was by directly using the
design system components as opposed to a more complete modal
implementation. Every colleague therefore had a lot of freedom when it
came to implementation of a modal. Properly implementing a modal so
that it's consistent with design and behaves in the least obstructive
way possible then becomes hard.
Our modals were incosistent with how disabled and loading states were
handled after clicking cancel and submit buttons, layout of those
buttons was sometimes different and when it came to modals with forms,
the form being reset after a successful submit caused a flicker while
the modal was fading out which was visually distracting.
I implemented a modal component to make implementing consistent modals
simpler, including support for forms, where all of the aforementioned
concerns were handled by default. These models can be seen in action
in the funding rounds calculator above.
Data integrity checks
Maintained product quality by implementing data integrity checks.See more...
My colleague implemented a framework for adding data integrity checks to notify us of instances where invalid data got into the database and
also automatically fix them if an automatic fix is possible. I wrote checks
dealing with detecting and autofixing trailing whitespace in attributes like
names, emails and domain specific identifiers which could cause subtle bugs
in filtering and matching items.
Linter rules
Maintained coding conventions and simplified codebase using linting rules as well as prevented subtle bugs.
See more...
This constant does not exist. Did you make a typo?
Maintained coding conventions and simplified codebase using linting rules as well as prevented subtle bugs. This involved playing around with
ASTs. I made linter rules for both frontend (ESLint) and backend (RuboCop).
Frontend one was about linting a stylistic error to follow our coding
conventions (i.e. no more pointing it out in code reviews). In the
backend, I made two linting rules which are a tad more complex:
OpenAPI helper enforcement - writing the OpenAPI specifications can be a bit cumbersome, there's
a lot of boilerplate that needs to be written making bigger types hundreds
of lines long. I wrote a helper to simplify this where majority of the
use cases that previously took anywhere from 1 to 10 lines could now be
reduced to a single line of code making it more readable. Converting the
whole OpenAPI specification to use this helper would a large effort for
an individual and there would still be the problem with my colleagues not
being aware of this helper and writing new specifications without it. This
linter rule therefore produced warnings (and errors in some cases)
to enforce its usage suggesting fellow colleagues to convert it to use
the helper. The idea was that the warnings would be changed to errors once the conversion is complete so that it would be written in this more
concise and readable format.
Check strings looking like constants actually exist - in Ruby, we were sometimes forced to write a constant, such as a class
name, as a string. The static type checker for Ruby, called Sorbet, we
used obviously didn't check strings and such strings sometimes became obsolete
when the class was refactored or removed and could cause an issue. This
linter rule checked that all strings that looked like such constants actually
referred to an existing constant in the codebase.
This constant does not exist. Did you make a typo?
Frontend to backend type validation
Increased developer's confidence and prevented minor bugs by ensuring
frontend matches backend.See more...
The OpenAPI specification was used to generate TypeScript types in the
frontend. Since the OpenAPI specification was written by hand, there
could be and were discrepancies between what was described in the
OpenAPI schema and the actual shape of the response that the server
returned. The idea of this initiative was to validate that the types in
the frontend, that were generated from the OpenAPI specification
manually defined in the backend, matched the actual response from the
server and in the case it didn't match, report an error to Sentry which
we used for frontend error tracking. As we didn't want to impose the
cost of validating the types on every request in production, it was only
enabled in our QA environment that we used often enough to catch
majority of these errors.
Frontend type:
typeExample = {
id: number
kind: 'good' | 'bad'
}
Server response:
{
id: '123'
kind: 'neutral'
unknown: 'smth'
}
Type validation output:
Property 'id' is not of type 'number'.
Property 'kind' does not match allowed values.
Unknown property 'unknown' found.
Conventional commit message auto formatter
Streamlined minor process by automatically formatting commit messages
according to conventional message format. See more...During my tenure, we have evolved our requirements for commit and
branch names as follows:
Standardized branch names and conventional commits required
everything recorded in Jira
Every code change needs to have a Jira associated and hence will
have standardized branch name and conventional commits
During the various stages, we have enforced this name both in CI as well
as in Git commit hooks for faster feedback. At the last stage, where every
change required a conventional commit message and happened on a branch called
something like
feat/JIRA-123-optional-description it became
a bit tedious to always include feat(JIRA-123):
in the commit message. I wrote a commit message Git hook that took the type
of the commit and scope (Jira ticket) from the branch name and formatted
an otherwise plain message according to the conventional commit specification.
Small time safer but mostly one less thing to think about.
The commit message "example message" is autoformatted based on the
branch name "feat/JIRA-123-example" to a final message reading
"feat(JIRA-123): example message"
2020-2022 (full-time from Feb 2022)
Announce app releases on Slack
Freed up engineering lead time by automating app release announcements in
Slack.See more...We used Heroku for hosting and deployed the latest Git main branch on every new change. We had a process in place where once a week
one of the engineering leads compiled a list of all the changes with their
Jira ID and a short description and posted this in a #release Slack channel.
I implemented a task that ran on every deployment to Heroku which compiled
a list of Jiras from the last deployment, got the Jira tasks descriptions,
the author (developer) of that change, found a related Slack account and
tagged them in the automated Slack message (developers were encouraged to
provide additional context, especially when it was related to a bug they
fixed for someone from a support of other department).
Grammar checker
Improved copy quality through automated grammar and spellchecking.See more...Using LanguageTool I implemented
a check in GitHub Actions that checked grammar including spellchecks. This
involved compiling the list of translations from a YML file into a human-readable
form which is what LanguageTool expects.
Example output for copy "This is highlight by diferent colour.":
highlight - possible grammar mistake highlighteddiferent - possible spelling mistake differentcolour - colour is British English color
Sentry (error tracking)
Enabled proactive identification and resolution of frontend issues by
setting up Sentry for error tracking.
OpenAPI
Laid the foundation for future enhancements such as automated TypeScript
type generation by adding OpenAPI.See more...Found the tooling and implemented the helpers for adding OpenAPI 2.0
(later upgraded to version 3.0) and implemented it on a few regular
cases as well as some exceptional ones. This was used later by the team
for generating TypeScript types for frontend which increased our
efficiency. It led to other initiatives, such as my work on linters and
frontend to backend type validation
later on to improve our workflow and get as much as possible from the coupling
of backend to frontend this afforded us.
2019-2020 (part-time)
Dependency management
Accelerated developer velocity for hospital task system app for nurses and
orderlies by streamlining dependency management and build system.See more...
The team had a strict policy for dependency management and actually
maintained their own repository of dependencies. This was initially done
through quite a large Git submodule. I've made it so that the team was able to use our own repository of
dependencies as if they were using the original repository which
simplified the workflow to a large extent.
Other
Gathered data that were previously compiled manually, automated minor
tasks.
Maintained high software quality by participating in acceptance
testing.
Skills
Frontend
React + Native
Javascript
Typescript
HTML
CSS
Tailwind
Astro
Expo
Next.js
React Router
pnpm
npm
Yarn
Jest
Playwright
Cypress
Swagger
Electron
Eleventy
Typst
Backend
Rails
Ruby
Go
C#
Python
Java
PostgreSQL
MySQL
Node
Bun
OpenAPI
Express
GraphQL
DevOps
Heroku
Google Cloud
Terraform
GitHub + Actions
Dependabot
Travis CI
TeamCity
Vercel
Cloudflare Pages
Netlify
Appwrite
Convex
Sentry
Datadog
AI & Productivity
Copilot
ChatGPT
Gemini
Claude
Notion
Figma
Miro
Stripe API
HubSpot API
Slack API
Development tools
Git
Docker
Cursor
IntelliJ
RubyMine
iTerm
DBeaver
Brave
Vite
Prettier
ESLint
Biome
Meetings & Processes
Stakeholder demos
Code reviews
1:1s
Hiring engineers
Technical design meetings
Performance reviews
Pair programming
Ideation sessions
RFC-like proposals
Retrospectives
Daily standup
Office hours
Grooming sessions
Colleague mentoring
Stakeholder demos
Code reviews
1:1s
Hiring engineers
Technical design meetings
Performance reviews
Pair programming
Ideation sessions
RFC-like proposals
Retrospectives
Daily standup
Office hours
Grooming sessions
Colleague mentoring
Education
Technical University of DenmarkCopenhagen, Denmark
Master of Computer Science and Engineering2020-2022