- Published on
Full-Stack Serverless MVP recipe for cash-trapped Startups.
Infinite independence + cost effectiveness + boundless flexibility = Happy Startups Everywhere!
What is an MVP?
No, we’re not talking about “most valuable player,” though that term could apply. We’re talking about Minimum Viable Product. And your Minimum Viable Product could become your Most Valuable Player, if you do your product road-mapping right.
Developing a new product is an expensive proposition. That’s easy enough to understand. With great cost comes great risk that the return on investment will be too little to justify the product’s development price tag. Cutting back on the features that go into a product is a big part of reducing risk with any new idea. The philosophy of MVP is to cut out everything except the core features an early customer would demand. The product is still valuable, because it allows the customer to solve some problems. So, the product is viable, but only minimally so.
Besides reduced risk, fewer features also mean a shorter runway to product launch. The MVP allows the developers and product owner to get feedback that much sooner. It’s almost like a new dimension of agile, iterative development — extending the development cycle into the marketplace for real-world reaction to the product idea to help the development team know whether or not they’re headed in the right direction. Design and development assumptions are put under the harsh light of public, end-user scrutiny.
Think of MVP as your first mega-sprint in the development cycle.
So what you need for is computing, data storage, hosting, content delivery.. etc. When it's come to Cloud providers I prefer to work with AWS because of their maturity in the field and their free tier.
What is Serverless Architecture?
Serverless doesn’t mean there are no servers! I agree, the term serverless is quite misleading. There are servers definitely, but you don’t have to worry about their scaling, maintenance, etc. Because computing resources are used as service. It can be classified as the third generation application architecture after monolith and microservices.
It is basically a cloud model where you pay only for the time your code is executed on the cloud infrastructure.
In other words, the serverless architecture allows developers to deploy their code in the form of small function packages. The platform on which the code runs is managed by the cloud providers.
These functions are executed in server-side stateless and event-triggered containers which are managed by third-party vendors. These functions are invoked by various external sources, like containers, database, storage, etc.
With the use of products such as AWS Lambda you don’t have to manage service discovery, container instance scaling and container-level logging which was essential in container driven microservice architecture.
This is exceptionally good for developers working in the startup as they can focus more on deployment part rather than maintenance.
Benefits of Serverless Architecture
There's a bunch of articles out there why serverless is awesome instead of telling the same thing over again I would like to give a more big picture for how all this connects each other to make an MVP.
Setting up an awesome MVP
Most of the MVP requires basic demands to be called MVP. such as a server, storage, auth flow etc.
Back-end:
- AWS AppSync for Graphql Server
Build data-driven apps with real-time and offline capabilities Appsync will be going to replace traditional REST API with Graphql.
Freebies The Free Tier offers the following monthly usage levels at no charge for 12 months after you sign up for an AWS account. 250,000 query or data modification operations 250,000 real-time updates 600,000 connection-minutes
- AWS Cognito for Auth Flow
Simple and Secure User Sign-Up, Sign-In, and Access Control
Freebies AWS Cognito charges per MAU(Monthly Active User) and first 50,000 MAU is free.
- AWS Lambda for custom business logic.
Run code without thinking about servers. Pay only for the compute time you consume.
Freebies The Lambda free tier includes 1M free requests per month and** 400,000 GB-seconds of computing time** per month.
- AWS DynamoDB for storage.
Fast and flexible NoSQL database service for any scale
Freebies 25 GB per month of data storage 200 million request per month 2.5 million stream requests per month
Front-end:
React
A JavaScript library for building user interfaces
React is the most common UI library out there and has a huge community.
Apollo Client
Apollo Client is the best way to use GraphQL to build client applications.
Which comes with a bunch of goodies such as,
- Declarative data fetching
if you have experience with React-redux you probably have seen ugly ajax request handling in redux actions which looks like.
1export const FETCH_JOB = 'FETCH_JOB'2export function fetchJob(job_name) {3return function (dispatch) {4dispatch(fetchingJob(job_name))5return axios6.get(searchApi(`/jobs/${job_name}`))7.then(function (response) {8return dispatch(jobFetched(response.data))9})10.catch(function (error) {11console.error(error)12return dispatch(jobFetchFailed(error))13})14}15}1617export const FETCHING_JOB = 'FETCHING_JOB'18export function fetchingJob(job_name) {19return {20type: FETCHING_JOB,21job_name,22}23}2425export const JOB_FETCHED = 'JOB_FETCHED'26export function jobFetched(job) {27return {28type: JOB_FETCHED,29job,30}31}3233export const JOB_FETCH_FAILED = 'JOB_FETCH_FAILED'34export function jobFetchFailed(error) {35return {36type: JOB_FETCH_FAILED,37message: 'error fetching job',38}39}
with Apollo-Client you can able copsulate this logic inside of component.
1const JobFeed = () => (2<Query query={GET_JOB}>3{({ loading, error, data }) => {4if (error) return <Error />5if (loading || !data) return <Fetching />67return <DogList dogs={data.dogs} />8}}9</Query>10)
which is more pleasenty to work with.
- Zero-config caching
Just by setting up Apollo Client, you get an intelligent cache out of the box with no additional configuration required .
- Combine local & remote data
Thanks to Apollo Client plugin apollo-link-state
you can handle app state without redux.
1const GET_JOB = gql`2query GetJobById($Id: String!) {3Job(Id: $Id) {4images {5url6id7}8isLiked @client9}10}11`
and you can get this state which ever component you need in the app.
AWS Amplify
The foundation for your cloud-powered mobile & web apps
AppSync Client with AWS Amplify to simplify user authentication workflows in your application. AppSync provides authentication using API Key, Cognito User Pools or AWS IAM policies and AWS Amplify complements the same with methods provided in Auth Class for user sign-up, sign-in, password confirmation and sign-out.
How you connect to AWS Appsync from front-end.
1import Amplify, { Auth } from 'aws-amplify';2import { withAuthenticator } from 'aws-amplify-react/dist/Auth';3import AWSAppSyncClient from 'aws-appsync';4import { ApolloProvider } from 'react-apollo';5const client = new AWSAppSyncClient({6url: 'https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql',7region: 'us-east-1',8auth: {9// AWS Cognito User Pool10type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,11jwtToken: async () =>12(await Auth.currentSession()).getIdToken().getJwtToken(),1314// API KEY15type: AUTH_TYPE.API_KEY,16apiKey: 'xxxxxxxxxxxxx',17// AWS IAM18type: AUTH_TYPE.AWS_IAM19credentials: () => Auth.currentCredentials(),20},21});22const WithProvider = () => (23<Router>24<ApolloProvider client={client}>25</Router>26);27export default withAuthenticator(WithProvider);
as a example of how its combines for login form is as follows;
1import React from 'react'2import { Button, Icon, message } from 'antd'3import { navigateTo } from 'gatsby-link'4import { Auth, Logger } from 'aws-amplify'5import { LoginForm } from '../../Form'67const logger = new Logger('Auth:Login')89// TODO: This should be a form.10class Login extends React.Component {11state = { status: 'idle' }1213onSuccess(message) {14logger.debug('login success', message)15this.setState({ status: 'success' })16navigateTo('/dashboard')17}1819onError(err) {20logger.error(err)21message.error(err.message)22this.setState({ status: 'error' })23}2425/**26* Log user in using cognito27*/28onCognitoLogin = (values) => {29logger.debug('login start')30this.setState({ status: 'loading' })3132Auth.signIn(values.email, values.password)33.then((success) => this.onSuccess(success))34.catch((err) => this.onError(err))35}3637render() {38return <LoginForm onSubmit={this.onCognitoLogin} centered="true" status={this.state.status} />39}40}4142export default Login
LoginForm.js
1import React from 'react'2import { Form, Input, Button, Col, Icon } from 'antd'3import { Logger } from 'aws-amplify'4import { intlShape, injectIntl } from 'react-intl'5import styled from 'styled-components'67import * as t from '../../i18n'89const logger = new Logger('Form:Login', 'DEBUG')1011const StyledForm = styled(Form)`12width: 200px;13margin: auto;1415.login-button {16width: 100%;17float: right;18}1920.ant-form-item {21margin-bottom: 5px;22}23`2425class Login extends React.Component {26state = { confirmDirty: false }2728onSubmit = (e) => {29e.preventDefault()30const { validateFields } = this.props.form3132validateFields((err, values) => {33if (err) {34return35}3637this.props.onSubmit(values)38})39}4041render() {42const { form, centered, initialValues, status } = this.props43const { getFieldDecorator } = form4445return (46<StyledForm centered={centered} status={status}>47<Form.Item>48{getFieldDecorator('email')(49<Input50placeholder="Email"51placeholder={this.props.intl.formatMessage({52id: 'signin.placeholder_email',53})}54/>55)}56</Form.Item>57<Form.Item>58{getFieldDecorator('password', {59rules: [{ required: true, message: 'Please input your password!' }],60})(61<Input62type="password"63placeholder={this.props.intl.formatMessage({64id: 'signin.placeholder_password',65})}66/>67)}68</Form.Item>69<Form.Item>70<Button71ghost72className="login-button"73type={status === 'error' ? 'danger' : 'primary'}74onClick={this.onSubmit}75>76{status === 'loading' ? <Icon type="loading" /> : t.signin.confirm}77</Button>78</Form.Item>79</StyledForm>80)81}82}8384Login.propTypes = {85intl: intlShape.isRequired,86}87export default Form.create()(injectIntl(Login))
Ant.design
A design system with values of Nature and Determinacy for the better user experience of enterprise applications
Being able to quickly build up UI of the app and making good looking is crucial for MVP products. That's why you don't want wasting time to build commonly used components from the ground to zero. that's why Antd
UI library gives you quick boost on that.
Styled-components
Visual primitives for the component age. Use the best bits of ES6 and CSS to style your apps without stress
Even we are using Ant.design
components we still need to get some custom styling to give our MVP some nice touch and feel. For that case styled-components is the nicest way to give a custom lock for components.
Netlify
Build, deploy, and manage modern web projects
Netlify comes really handy for MVP deployments. with the quick setup you can add git hooks for auto-build your code and deploy.
Every time you open a pull request, or push new changes to a branch, Netlify automatically builds a preview with a unique URL. Like a staging environment for every PR or branch, previews are perfect for testing and collaboration.
With the tools & libraries listed above with the team of 3 developers we have managed to pull off challenging MVP product AMZ Kungfu - Amazon Seller Ads Automation Platform
Which is made of;
16 DynamoDB tables
28 Lambda
6 AWS SNS Topic
4 AWS SQS Queue
- Fully Automated and Reactive Data Pipeline with Serverless via AWS Lambda. More than 500k records are synced into DynamoDB in minutes. Also at an extremely low cost of $0 for all the computing power.
- Seamless automation that reacts to the data change. As soon as data is updated, we optimize the bidding strategy for the Amazon seller ads.
- Rapid prototyping with AWS AppSync GraphQL and React. Data is synced to the frontend and rendered into graphs to provide insight for the sellers. in less than 2 months.
Stay tuned if you wanna hear in detail how we achieve this.