Feature Voting Board for SaaS with React and DynamoDB

A more appealing, readable, and up-to-date version of this story is available on my personal blog.

Why Adding Feature Voting Board

Making Board with React

The whole board is a bunch of React components. On the first render, the app requests all the features, and once they arrived it renders them in different columns according to status.


const FeaturesBoard = ({ features, width, requestFeatures }) => {
// ...
useEffect(() => {
}, [requestFeatures])
const withStatuses = statuses =>
features.filter(({ status }) => statuses.includes(status))
const shouldShowThreeColumns = width > THREE_COLUMNS

// ...
return (
{shouldShowThreeColumns ? (
) : (

To do asynchronous operations I use redux-saga. There are only four types of requests related to the board.


export function* requestFeatures() {
const data = yield callApi(REQUEST_FEATURE_MUTATION)
yield put(receiveFeatures(data.features))
export function* upvoteFeature({ payload }) {
yield callApi(UPVOTE_FEATURE_MUTATION(payload))
export function* reupvoteFeature({ payload }) {
yield callApi(REUPVOTE_FEATURE_MUTATION(payload))
export function* submitFeature() {
const state = yield select()
const { featureName, featureDescription } = state.features
const variables = {
input: {
name: featureName,
description: featureDescription
const { submitFeature } = yield callApi(
yield put(receiveFeature(submitFeature))
yield put(clearFeatureForm())

NodeJS + DynamoDB Backend

Right now, the GraphQL resolver that returns all the features isn’t the most efficient because after querying all the items, it iterates over every item’s list of upvotes. It is OK for Increaser because there are not that many features or upvotes. There always tradeoffs when it comes to the NoSQL database. One of the solutions to make the query more effective is to have duplication of data and store in the user’s item the list of features he upvoted.


const features = async (_, __, { userId }) => {
const allFeatures = await featuresTable.getAll()
const filteredFeatures = allFeatures.filter(
({ status, ownerId }) =>
!(status === STATUS.WAITING_FOR_CONFIRMATION && ownerId !== userId)
return filteredFeatures.map(f => ({
id: f.id,
status: f.status,
name: f.name,
description: f.description,
upvotesNumber: f.upvotedBy.length,
upvoted: userId && f.upvotedBy.includes(userId)

Telegram Notifications + Automatic Emails


const { processRecord } = require('./watcher')exports.handler = async ({ Records }, context, callback) => {
await Promise.all(Records.map(processRecord))
callback(null, `Successfully processed ${Records.length} records.`)

To send emails, I use AWS SES. Previously I was sending a fancy message, but now I stick to a regular text in all my emails to have less chance to appear in the Promotion tab. To get notification about a newly proposed feature, I send a Telegram message to the geekrodion-helper channel.


Two of the features in the “Done” actually were removed from the app after some time. I haven’t thought very well how they could fit in the product and ended up wasting time. At the same time, there were a few features that were proposed by people, got implemented, and enhanced the app.

The thing is that your customers won’t think for a long time about a feature and how it fits in the product — that’s our job. I think it is good to pay attention to signals that come from the user, yet we should try to understand why they want that feature and does it makes sense to invest energy to bring the proposed idea to the product. Extra features do not always make the app better — they could make your product harder to understand and mess up your positions, so be careful.

Reach the next level of focus and productivity with increaser.org.