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.

freetier.png

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.

1
export const FETCH_JOB = 'FETCH_JOB'
2
export function fetchJob(job_name) {
3
return function (dispatch) {
4
dispatch(fetchingJob(job_name))
5
return axios
6
.get(searchApi(`/jobs/${job_name}`))
7
.then(function (response) {
8
return dispatch(jobFetched(response.data))
9
})
10
.catch(function (error) {
11
console.error(error)
12
return dispatch(jobFetchFailed(error))
13
})
14
}
15
}
16
17
export const FETCHING_JOB = 'FETCHING_JOB'
18
export function fetchingJob(job_name) {
19
return {
20
type: FETCHING_JOB,
21
job_name,
22
}
23
}
24
25
export const JOB_FETCHED = 'JOB_FETCHED'
26
export function jobFetched(job) {
27
return {
28
type: JOB_FETCHED,
29
job,
30
}
31
}
32
33
export const JOB_FETCH_FAILED = 'JOB_FETCH_FAILED'
34
export function jobFetchFailed(error) {
35
return {
36
type: JOB_FETCH_FAILED,
37
message: 'error fetching job',
38
}
39
}

with Apollo-Client you can able copsulate this logic inside of component.

1
const JobFeed = () => (
2
<Query query={GET_JOB}>
3
{({ loading, error, data }) => {
4
if (error) return <Error />
5
if (loading || !data) return <Fetching />
6
7
return <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.

1
const GET_JOB = gql`
2
query GetJobById($Id: String!) {
3
Job(Id: $Id) {
4
images {
5
url
6
id
7
}
8
isLiked @client
9
}
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.

1
import Amplify, { Auth } from 'aws-amplify';
2
import { withAuthenticator } from 'aws-amplify-react/dist/Auth';
3
import AWSAppSyncClient from 'aws-appsync';
4
import { ApolloProvider } from 'react-apollo';
5
const client = new AWSAppSyncClient({
6
url: 'https://xxxx.appsync-api.us-east-1.amazonaws.com/graphql',
7
region: 'us-east-1',
8
auth: {
9
// AWS Cognito User Pool
10
type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
11
jwtToken: async () =>
12
(await Auth.currentSession()).getIdToken().getJwtToken(),
13
14
// API KEY
15
type: AUTH_TYPE.API_KEY,
16
apiKey: 'xxxxxxxxxxxxx',
17
// AWS IAM
18
type: AUTH_TYPE.AWS_IAM
19
credentials: () => Auth.currentCredentials(),
20
},
21
});
22
const WithProvider = () => (
23
<Router>
24
<ApolloProvider client={client}>
25
</Router>
26
);
27
export default withAuthenticator(WithProvider);

as a example of how its combines for login form is as follows;

1
import React from 'react'
2
import { Button, Icon, message } from 'antd'
3
import { navigateTo } from 'gatsby-link'
4
import { Auth, Logger } from 'aws-amplify'
5
import { LoginForm } from '../../Form'
6
7
const logger = new Logger('Auth:Login')
8
9
// TODO: This should be a form.
10
class Login extends React.Component {
11
state = { status: 'idle' }
12
13
onSuccess(message) {
14
logger.debug('login success', message)
15
this.setState({ status: 'success' })
16
navigateTo('/dashboard')
17
}
18
19
onError(err) {
20
logger.error(err)
21
message.error(err.message)
22
this.setState({ status: 'error' })
23
}
24
25
/**
26
* Log user in using cognito
27
*/
28
onCognitoLogin = (values) => {
29
logger.debug('login start')
30
this.setState({ status: 'loading' })
31
32
Auth.signIn(values.email, values.password)
33
.then((success) => this.onSuccess(success))
34
.catch((err) => this.onError(err))
35
}
36
37
render() {
38
return <LoginForm onSubmit={this.onCognitoLogin} centered="true" status={this.state.status} />
39
}
40
}
41
42
export default Login

LoginForm.js

1
import React from 'react'
2
import { Form, Input, Button, Col, Icon } from 'antd'
3
import { Logger } from 'aws-amplify'
4
import { intlShape, injectIntl } from 'react-intl'
5
import styled from 'styled-components'
6
7
import * as t from '../../i18n'
8
9
const logger = new Logger('Form:Login', 'DEBUG')
10
11
const StyledForm = styled(Form)`
12
width: 200px;
13
margin: auto;
14
15
.login-button {
16
width: 100%;
17
float: right;
18
}
19
20
.ant-form-item {
21
margin-bottom: 5px;
22
}
23
`
24
25
class Login extends React.Component {
26
state = { confirmDirty: false }
27
28
onSubmit = (e) => {
29
e.preventDefault()
30
const { validateFields } = this.props.form
31
32
validateFields((err, values) => {
33
if (err) {
34
return
35
}
36
37
this.props.onSubmit(values)
38
})
39
}
40
41
render() {
42
const { form, centered, initialValues, status } = this.props
43
const { getFieldDecorator } = form
44
45
return (
46
<StyledForm centered={centered} status={status}>
47
<Form.Item>
48
{getFieldDecorator('email')(
49
<Input
50
placeholder="Email"
51
placeholder={this.props.intl.formatMessage({
52
id: 'signin.placeholder_email',
53
})}
54
/>
55
)}
56
</Form.Item>
57
<Form.Item>
58
{getFieldDecorator('password', {
59
rules: [{ required: true, message: 'Please input your password!' }],
60
})(
61
<Input
62
type="password"
63
placeholder={this.props.intl.formatMessage({
64
id: 'signin.placeholder_password',
65
})}
66
/>
67
)}
68
</Form.Item>
69
<Form.Item>
70
<Button
71
ghost
72
className="login-button"
73
type={status === 'error' ? 'danger' : 'primary'}
74
onClick={this.onSubmit}
75
>
76
{status === 'loading' ? <Icon type="loading" /> : t.signin.confirm}
77
</Button>
78
</Form.Item>
79
</StyledForm>
80
)
81
}
82
}
83
84
Login.propTypes = {
85
intl: intlShape.isRequired,
86
}
87
export 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.

styled.png

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. ppc_screenshot_05.pngppc_screenshot_06.png

Stay tuned if you wanna hear in detail how we achieve this.