Frameworkless Web Applications

Since we have (mostly) advanced beyond CGI scripts and PHP the default tool many people reach for when building a web application is a framework. Like drafting a standard legal contract or making a successful Hollywood film, it’s good to have a template to work off of. A framework lends structure to your application and saves you from having to reinvent a bunch of wheels. It’s a solid foundation to build on which can be a substantial “batteries included” model (Rails, Django, Spring Boot, Nest) or a lightweight “slap together whatever shit you need outta this” sort of deal (Flask, Express).

Foundations can be handy.

The idea of a web framework is that there are certain basic features that most web apps need and that these services should be provided as part of the library. Nearly all web frameworks will give you some custom implementation of some or all of:

  • Configuration
  • Logging
  • Exception trapping
  • Parsing HTTP requests
  • Routing requests to functions
  • Serialization
  • Gateway adaptor (WSGI, Rack, WAR)
  • Authentication
  • Middleware architecture
  • Plugin architecture
  • Development server

There are many other possible features but these are extremely common. Just about every framework has its own custom code to route a parsed HTTP request to a handler function, as in “call hello() when a GET request comes in for /hello.”

There are many great things to say about this approach. The ability to run your application on any sort of host from DigitalOcean to Heroku to EC2 is something we take for granted, as well as being able to easily run a web server on your local environment for testing. There is always some learning curve as you learn the ins and outs of how you register a URL route in this framework or log a debug message in that framework or add a custom serializer field.

But maybe we shouldn’t assume that our web apps always need to be built with a framework. Instead of being the default tool we grab without a moment’s reflection, now is a good time to reevaluate our assumptions.

Serverless

What struck me is that a number of the functions that frameworks provide are not needed if I go all-in on AWS. Long ago I decided I’m fine with Bezos owning my soul and acceded to writing software for this particular vendor, much as many engineers have built successful applications locked in to various layers of software abstraction. Early programmers had to decide which ISA or OS they wanted to couple their application to, later we’re still forced to make non-portable decisions but at a higher layer of abstraction. My python or JavaScript code will run on any CPU architecture or UNIX OS, but features from my cloud provider may restrict me to that cloud. Which I am totally fine with.

I’ve long been a fan of and written about serverless applications on my blog because I enjoy abstracting out as much of my infrastructure as possible so as to focus on the logic of my application that I’m interested in. My time is best spent concerning myself with business logic and not wrangling containers or deployments or load balancer configurations or gunicorn.

We’ve had a bit of a journey over the years adopting the serverless mindset, but one thing has been holding me back and it’s my attachment to web frameworks. While it’s quite common and appropriate to write serverless functions as small self-contained scripts in AWS Lambda, building a larger application in this fashion feels like trying to build a house without a foundation. I’ve done considerable experimentation mostly with trying to cram Flask into Lambda, where you still have all the comforts of your familiar framework and it handles all the routing inside a single function. You also have the flexibility to easily take your application out of AWS and run it elsewhere.

There are a number of issues with the approach of putting a web framework into a Lambda function. For one, it’s cheating. For another, when your application grows large enough the cold start time becomes a real problem. Web frameworks have the side-effect of loading your entire application code on startup, so any time a request comes in and there isn’t a warm handler to process it, the client must wait for your entire app to be imported before handling the request. This means users occasionally experience an extra few seconds of delay on a request, not good from a performance standpoint. There are simple workarounds like provisioned concurrency but it is a clear sign there is a flaw in the architecture.

Classic web frameworks are not appropriate for building a truly serverless application. It’s the wrong tool for the architecture.

The Anti-Framework

Assuming you are fully bought in to AWS and have embraced the lock-in lifestyle, life is great. AWS acts like a framework of its own providing all of the facilities one needs for a web application but in the form of web services of the Amazonian variety. If we’re talking about RESTful web services, it’s possible to put together an extremely scalable, maintainable, and highly available application.

No docker, kubernetes, or load balancers to worry about. You can even skip the VPC if you use the Aurora Data API to run SQL queries.

The above list could go on for a very long time but you get the point. If we want to be as lazy as possible and leverage cloud services as much as possible then what we really want is a tool for composing these services in an expressive and familiar fashion. Amazon’s new Cloud Development Kit (CDK) is just the tool for that. If you’ve never heard of CDK you can read a friendly introduction here or check out the official docs.

In short CDK lets you write high-level code in Python, TypeScript, Java or .NET, and compile it to a CloudFormation template that describes your infrastructure. A brief TypeScript example from cursed-webring:

// API Gateway with CORS enabled
const api = new RestApi(this, "cursed-api", {
  restApiName: "Cursed Service",
  defaultCorsPreflightOptions: {
    allowOrigins: apigateway.Cors.ALL_ORIGINS,
  },
  deployOptions: { tracingEnabled: true },
});

// defines the /sites/ resource in our API
const sitesResource = api.root.addResource("sites");

// get all sites handler, GET /sites/
const getAllSitesHandler = new NodejsFunction(
  this,
  "GetCursedSitesHandler",
  {
    entry: "resources/cursedSites.ts",
    handler: "getAllHandler",
    tracing: Tracing.ACTIVE,
  }
);
sitesResource.addMethod("GET", new LambdaIntegration(getAllSitesHandler));

Is CDK a framework? It depends how you define “framework” but I consider more to be infrastructure as code. By allowing you to effortlessly wire up the services you want in your application, CDK more accurately removes the need for any sort of traditional web framework when it comes to features like routing or responding to HTTP requests.

While CDK provides a great way to glue AWS services together it has little to say when it comes to your application code itself. I believe we can sink even lower into the proverbial couch by decorating our application code with metadata that generates the CDK resources our application declares, specifically Lambda functions and API Gateway routes. I call it an anti-framework.

@JetKit/CDK

To put this into action we’ve created an anti-framework called @jetkit/cdk, a TypeScript library that lets you decorate functions and classes as if you were using a traditional web framework, with AWS resources automatically generated from application code.

The concept is straightforward. You write functions as usual, then add metadata with AWS-specific integration details such as Lambda configuration or API routes:

import { HttpMethod } from "@aws-cdk/aws-apigatewayv2"
import { Lambda, ApiEvent } from "@jetkit/cdk"

// a simple standalone function with a route attached
export async function aliveHandler(event: ApiEvent) {
  return "i'm alive"
}
// define route and lambda properties
Lambda({
  path: "/alive",
  methods: [HttpMethod.GET],
  memorySize: 128,
})(aliveHandler)

If you want a Lambda function to be responsible for related functionality you can build a function with multiple routes and handlers using a class-based view. Here is an example:

import { HttpMethod } from "@aws-cdk/aws-apigatewayv2"
import { badRequest, methodNotAllowed } from "@jdpnielsen/http-error"
import { ApiView, SubRoute, ApiEvent, ApiResponse, ApiViewBase, apiViewHandler } from "@jetkit/cdk"

@ApiView({
  path: "/album",
  memorySize: 512,
  environment: {
    LOG_LEVEL: "DEBUG",
  },
  bundling: { minify: true, metafile: true, sourceMap: true },
})
export class AlbumApi extends ApiViewBase {
  // define POST handler
  post = async () => "Created new album"

  // custom endpoint in the view
  // routes to the ApiViewBase function
  @SubRoute({
    path: "/{albumId}/like", // will be /album/123/like
    methods: [HttpMethod.POST, HttpMethod.DELETE],
  })
  async like(event: ApiEvent): ApiResponse {
    const albumId = event.pathParameters?.albumId
    if (!albumId) throw badRequest("albumId is required in path")

    const method = event.requestContext.http.method

    // POST - mark album as liked
    if (method == HttpMethod.POST) return `Liked album ${albumId}`
    // DELETE - unmark album as liked
    else if (method == HttpMethod.DELETE) return `Unliked album ${albumId}`
    // should never be reached
    else return methodNotAllowed()
  }
}

export const handler = apiViewHandler(__filename, AlbumApi)

The decorators aren’t magical; they simply save your configuration as metadata on the class. It does the same thing as the Lambda() function above. This metadata is later read when the corresponding CDK constructs are generated for you. ApiViewBase contains some basic functionality for dispatching to the appropriate method inside the class based on the incoming HTTP request.

Isn’t this “routing?” Sort of. The AlbumApi class is a single Lambda function for the purposes of organizing your code and keeping the number of resources in your CloudFormation stack at a more reasonable size. It does however create multiple API Gateway routes, so API Gateway is still handling the primary HTTP parsing and routing. If you are a purist you can of course create a single Lambda function per route with the Lambda() wrapper if you desire. The goal here is simplicity.

The reason Lambda() is not a decorator is that function decorators do not currently exist in TypeScript due to complications arising from function hoisting.

Why TypeScript?

As an aside, TypeScript is now my preferred choice for backend development. JavaScript no, but TypeScript yes. The rapid evolution and improvements in the language with Microsoft behind it have been impressive. The language is as strict as you want it to be. Having one set of tooling, CI/CD pipelines, docs, libraries and language experience in your team is much easier than supporting two. All the frontends we work with are React and TypeScript, why not use the same linters, type checking, commit hooks, package repository, formatting configuration, and build tools instead of maintaining say, one set for a Python backend and another for a TypeScript frontend?

Python is totally fine except for its lack of type safety. Do not even attempt to blog at me ✋🏻 about mypy or pylance. It is like saying a Taco Bell is basically a real taqueria. Might get you through the day but it’s not really the same thing 🌮

Construct Generation

So we’ve seen the decorated application code, how does it get turned into cloud resources? With the ResourceGeneratorConstruct, a CDK construct that takes your functions and classes as input and generates AWS resources as output.

import { CorsHttpMethod, HttpApi } from "@aws-cdk/aws-apigatewayv2"
import { Construct, Duration, Stack, StackProps, App } from "@aws-cdk/core"
import { ResourceGeneratorConstruct } from "@jetkit/cdk"
import { aliveHandler, AlbumApi } from "../backend/src"  // your app code

export class InfraStack extends Stack {
  constructor(scope: App, id: string, props?: StackProps) {
    super(scope, id, props)

    // create API Gateway
    const httpApi = new HttpApi(this, "Api", {
      corsPreflight: {
        allowHeaders: ["Authorization"],
        allowMethods: [CorsHttpMethod.ANY],
        allowOrigins: ["*"],
        maxAge: Duration.days(10),
      },
    })

    // transmute your app code into infrastructure
    new ResourceGeneratorConstruct(this, "Generator", {
      resources: [AlbumApi, aliveHandler], // supply your API views and functions here
      httpApi,
    })
  }
}

It is necessary to explicitly pass the functions and classes you want resources for to the generator because otherwise esbuild will optimize them out of existence.

Try It Out

@jetkit/cdk is MIT-licensed, open-source, and has documentation and great tests. It doesn’t actually do much at all and that’s the point.

If you want to try it out as fast as humanly possible you can clone the TypeScript project template to get a modern serverless monorepo using NPM v7 workspaces.

Woodworker Designs and Builds the Perfect Tiny House Boat called the Le Koroc
Maybe a foundation isn’t needed after all

The Web Developer’s Checklist Manifesto

Dmitry Spiridonov 9 min read

Surgeons and military leaders use checklists to ensure consistently high levels of quality in mission-critical work. So why don’t we use them more in software?

We can. And should. Here’s how –

In my job at JetBridge, I work with some of the most experienced software development managers and some of the most demanding enterprise clients around the World. Over the years I’ve developed a checklist for releasing new website builds that will save you time and potential embarrassment. Use this consistently and your coworkers and managers will think you’re a “rock star.”

But before we jump into the checklist I assume that you have done all the development work in terms of the website functionality and its content, you have reviewed all the pages yourself or with the client and your next big step is releasing it to the world.

Checklist:

1. Set up a staging environment

Whether you want to test a new feature on the website, show it to the client, or run performance tests, you should have a staging environment set up. A stage environment is an environment for testing that exactly resembles a production environment.

2. Set up monitoring

Whether your hosting provider drops the ball or you accidentally publish a change that breaks one of your pages or the whole website, setting up an uptime monitoring tool will help you get notified about the issue by Text/Email/Slack message in a timely fashion. Some popular services include: sentry.io, uptimerobot.com, statuscake.com.

3. Compress images

To optimize website load time and improve its SEO, optimize the images used on the website. The primary goal of formatting your images is to find the balance between the lowest file size and acceptable quality. You can use tools like imageoptim.com for the optimization of your images.

4. Run the Google Lighthouse test and address reported issues

Lighthouse from Google is a tool that audits load times, accessibility and search engine optimization of web pages. It includes the ability to test progressive web applications for compliance with standards and best practices. Make sure to run the Lighthouse on a deployed environment (dev/stage) and not only locally.

Run the Google Lighthouse test

5. Set up a 404 page

What happens if the user goes to a non-existing page accidentally or because of a broken link? Will the user see this unfriendly message?

Set up a 404 page

Take some time to design a nice 404 page that will allow the user to go the main page of the website:

6. Set up a favicon

Favicon helps your website appear more professional and credible and can be found among all the other tabs more easily.

To add a favicon you can create a .png image and then use one of the following snippets between the <head> tags of your static HTML documents:

<link rel="icon" type="image/png" href="/favicon.png" />

7. Set up Meta Titles, Meta Description and Social Preview Links

Titles help search engines to serve the right results to the user. Moreover, they display the page title to the users in search results so that they can determine whether the page is something they are looking for. As with the favicon, page titles help the user navigate through the open tabs.

Meta description displays the description of your page in search results and while sharing your page on social media.

To add meta tags, describe metadata within HTML document:

<head>
  <title>HTML Elements Reference</title>
  <meta name="description" content="Free Web tutorials">
</head>

Setting up Social Preview links:

Facebook uses <meta> tags leveraging the Open Graph protocol, a classification system for web pages that extends beyond those <meta> tags already defined in HTML5. A complete list of <meta> tags available can be found at the Open Graph Web site. There are so many from which to choose that it can be somewhat intimidating, but only four are actually required:

<meta property="og:title" content="European Travel Destinations">

<meta property="og:description" content="Offering tour packages for individuals or groups.">

<meta property="og:image" content="<http://euro-travel-example.com/thumbnail.jpg>">

<meta property="og:url" content="<http://euro-travel-example.com/index.htm>"

Twitter has its own <meta> tags that are similar to the Open Graph protocol, but uses the “twitter” prefix instead of “og”. As with Facebook, only a few are required:

<meta name="twitter:title" content="European Travel Destinations ">

<meta name="twitter:description" content=" Offering tour packages for individuals or groups.">

<meta name="twitter:image" content=" <http://euro-travel-example.com/thumbnail.jpg>">

<meta name="twitter:card" content="summary_large_image">

8. Create a robots.txt file

robots.txt contains instructions for search engine crawlers regarding the pages that are allowed to be crawled. It’s useful if you want to exclude certain pages from crawling. The presence of the robots.txt file positively influences your SEO. You should put your robots.txt at the root of the website host to which it applies. More about robots.txt: https://moz.com/learn/seo/robotstxt

9. Set up XML sitemaps/HTML sitemap

A sitemap is a file where you provide information about the pages, videos, and other files on your site, and the relationships between them. Search engines like Google read this file to more intelligently crawl your site. A sitemap tells Google which pages and files you think are important in your site, and also provides valuable information about these files: for example, for pages, when the page was last updated, how often the page is changed, and any alternate language versions of a page.

Guide on building and submitting a sitemap: https://support.google.com/webmasters/answer/183668?hl=en

10. Configure active environment variables for the production build

Make sure that you have configured the environment variables (payment service provider publishable keys, backend endpoint URL, etc.) for your build. Remember to never store any secret keys on GitHub due to security reasons. You can use a library like dotenv package to configure you environment variables.

11. Test the website in different browsers, ensuring responsive design on varied devices

Use a staging environment that you’ve set up to do testing on different devices. Make sure that all the functionality of your website is working, make sure you don’t have broken links. Keep in mind that your pages may behave differently on mobile as the interaction on mobile devices is done using touches (not clicks).

You can use a test from Google to test if a particular page is mobile-friendly: https://search.google.com/test/mobile-friendly

I strongly recommend you to use real mobile devices to test the website interactivity instead of just using browsers’ developer tools.

12. Set-up a domain/transfer a domain to your hosting provider/set the nameservers to point to your hosting provider

If your domain registrar is different from your hosting provider – you will have to perform a domain transfer. Migrating a domain to AWS Route53:

https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-transfer-to-route-53.html

Alternatively, instead of transferring the domain, you can make your hosting provider the DNS service for a domain that’s in use. Instructions on Making Route 53 the DNS service for a domain that’s in use for AWS:

https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/migrate-dns-domain-in-use.html

13. Set up a CDN (Content Delivery Network)

A CDN is a really fast way to deliver static content from your website or mobile application to people with regional caching. A CDN is made up of a network of servers (“points of presence” or POPs) in locations all over the world.

Setting up CDN using

Cloudflare: https://support.cloudflare.com/hc/en-us/articles/201720164-Creating-a-Cloudflare-account-and-adding-a-website

AWS Cloudfront (S3): https://aws.amazon.com/cloudfront/getting-started/S3/

14. Set Up an SSL certificate

An SSL certificate encrypts the data that goes from a user’s computer to the target website and back. Every time a user enters information into your site, SSL makes sure it can securely travel from their browser to your web server.

Installing SSL on Cloudfront: https://aws.amazon.com/premiumsupport/knowledge-center/install-ssl-cloudfront/

Getting an SSL certificate on Letsencrypt: https://letsencrypt.org/getting-started/

15. Configure payment processing

If your website accepts payments, you should have a Payment Service Provider configured on your website. Make sure to set up publishable keys for your PSP before deploying the website to production.

16. Link to the terms of service and privacy policy on your website

There are several reasons for having a privacy policy, including that it is a legal requirement – you can be fined if you don’t have one. In addition, having a privacy policy also helps with SEO.

While a Terms and Conditions agreement is recommended to have for your website, it’s not required by law to have this agreement. This agreement sets the rules that users must agree to in order to use your website. Some reasons for having terms of use: prevent abuses, own your Content, terminate accounts, limit liability, set the governing law. If your company or your client doesn’t have a dedicated lawyer to prepare these documents, you can look for Privacy Policy and Terms and Conditions generators on the Internet (make sure to review and edit them to suit your service).

You may want to extend this list with other items specific to the industry you work in or business processes of your company. Happy shipping 🚢!

Forbes: An Insider’s Guide To Outsourcing And Outstaffing Software Development

John Sung Kim 4 min read

Originally posted on Forbes.com

As many say costs for software developers have soared across the U.S. and Western Europe, outsourcing to arbitrage labor costs has not just become popular but also increasingly necessary.

To build two SaaS companies in the Bay Area, I’ve leveraged offshore development teams to a degree of success. I also had a chance to be a marketing consultant for a leading online marketplace for developers and am the CEO of an outsourcing company, so I’ve become intimately familiar with this industry over my career.

There are many talented, ethical agencies and developers across the world; however, just like in any market, extensive due diligence by the client is a must. Before hiring outsourcers to build your app or hiring recruiters to source offshore labor, here are three insider observations you might consider:

1. Outsourcers, outstaffers and recruiters commonly portray interns as juniors, juniors as mid-levels and mid-levels as seniors. Because this practice has become so widespread, I’ve found there is market pressure on many good vendors to sell their staff this way. So clients should manage their projects intensely on a daily basis to ensure consistency and quality.

2. Failure rates for apps built by outsourcers can be incredibly high. While there is no published data yet, I’ve seen very few examples of outsourcers who have built and launched a commercially successful product on behalf of a client as a turnkey service.

3. I’ve seen some recruiters start to offer research and development (R&D) center services to clients to compete with outsourcers. This new trend of not only sourcing the developers but operating the office and administrative functions on behalf of the client have additional fees. Over time, this could reduce labor costs for the clients, but clients need to make a bigger upfront commitment.

Given the state of the current market, I suggest implementing these best practices before engaging any outsourcing, outstaffing or recruiter firm:

1. For outsourcers building the entire turnkey app for you, ask for the references of at least two software applications they have launched successfully. Code is only one part of what makes a software application successful, so if the response is “we have none” but you still like the firm, you can engage your product or project manager to manage the outsourced staff directly.

Also, ask the references how the outsourcer accommodated change requests. Your app’s scope or project is likely to change multiple times. You want a partner who is adaptive, but being adaptive in IT can be as much about the outsourcer’s culture as it is about their process.

2. If you’re looking to outstaff to add to your existing product and team through recruiters or R&D centers, you may want to intensely focus on the interview questions and test tasks assigned to the candidates. For example, when I ask a candidate, “Why do you want to work for us?” and the answer is something akin to “for more money” and not “I want to work for a ‘product’ company,” that developer never lasts with us for more than a year.

I’ve also learned to ask what local conferences or Meetups the candidates belong to. Voluntary self-education is one of the leading indicators I’ve seen in how much value a developer will bring to our projects over the long run.

But by far, the “marquee” question I ask every developer is, “Have you contributed to any open-source projects?” While not every top developer contributes to open-source projects, I’ve noticed that many open-source contributors are top developers.

It may be harder to find developers who contribute to open-source projects in emerging economies, according to a GitHub open-source contribution map, but we’ve found this is the single biggest determinant (other than cultural fit) of whether a developer will produce at an elite level.

3. Go through a trial period without firm commitments, and if the developers are to your liking, prepare to offer a term commitment of a year or more to the vendor. In my experience, outsourcers and outstaffers often have their developers sitting “on the bench” some of the time, so term commitments mean they don’t have to suffer a margin loss — and you could negotiate some of this savings in reduced fees.

As a negotiating tactic, you can also offer to prepay their wages quarterly or every six months.

4. Look for providers that focus on both a vertical and a software language. I believe the vendors that are efficient with your time and money are so because they focus on a specific vertical — like fintech or mobile games — and have expertise in one or two languages.

I recommend avoiding shops that claim to be experts in “Java, Python, C++, and AI/ML” and that spam you with faces of attractive females representing their agency’s expertise. These are often bottom-of-the-barrel operators. I’ve found that most of the good shops specialize in a vertical and specialize in a language, and they have plenty of work from referrals — so they don’t need to spam anyone.

5. Negotiate the monthly wages and the buyout fee if you decide to make the developers a part of your permanent team. Outsourcers and outstaffers need to charge more than what they pay the developers to make reasonable net margins.

Sales costs, office space, recruiting and bench-time contribute to overhead. In my experience, average buyout fees range from one month of wages to one year, so negotiate — as the market prices range drastically.

As Bloomberg suggests, the demand for talented and experienced tech talent in the U.S. continues to rise, and the BLS reports that unemployment for the professional, scientific, and technical services sector was at just 2.5% in December 2018 — so sourcing labor from other countries may become a necessity, even for Series B- and C-funded startups. Understanding nuances of the regions where you hire and following some best practices should save you a lot of time and money — and maybe some midnight migraines as well.