• Stars
    star
    114
  • Rank 308,031 (Top 7 %)
  • Language
    TypeScript
  • Created over 7 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A starter kit repository for building single page webapps in TypeScript with react, redux, graphql/express, jest

Setup

  • Install Node 10 LTS and yarn. Older or newer versions may or may not work. (Recommend nvm and brew install yarn --without-node on mac.)
  • Install Docker.app. Our database and other services are configured to run in docker.
  • Symlink .env.example to .env, which sets up your environment to run from Docker. You can copy and modify .env.example to .env if the defaults won't work for you.
  • Start postgres with: docker-compose up. This will set up a postgres docker image and start it.
  • Run yarn to install dependencies.
  • Run yarn db:create to create development and test databases.
  • Run yarn build to build the application, including supporting scripts.

Note: Start docker-compose up and leave it running any time you want to run the app/tests.

Running the app

This repository includes a simple redux/graphql-based app. If you'd like to play around with this example, here's how to start it up:

  • Run yarn build to, among other things, create generated types and scripts.
  • Run yarn db:migrate:latest to migrate your development and test databases.
  • Optionally run yarn db:migrate-and-seed to add some test data to your dev database.
  • Run yarn dev to start the hot-reloading dev server, which can be visited on port 3000.
  • See the interactive style guide and component tests by running yarn dev:storybook and visit localhost:9001.
  • To run unit tests, run yarn test:unit or yarn test:unit --watch for the interactive jest-based test runner.

For this project, we have decided not to check in the generated GraphQL types. This means that you'll need to run yarn build before you begin viewing or editing the source code, lest you see spurious type errors and failed imports.

Stack

This project is a single-page webapp using the following technologies:

  • TypeScript ย โ€“ย a type-safe variant of JavaScript from Microsoft which reduces errors and improves IDE/editor support over regular JavaScript.
  • Node.js โ€“ powers our server, and is pinned to the latest LTS release.
  • Express โ€“ our HTTP server, which is lightly used only to host our GraphQL API.
  • GraphQL โ€“ย an alternative to REST apis which supports a demand-driven architecture. Our GraphQL server is Apollo GraphQL server.
  • Jest for unit testing.
  • Webpack โ€“ builds our application for our various deployment targets.
  • Apollo Link State for client state management.
  • Nightmare.js for acceptance testing.
  • React Storybook for component documentation and style guides.
  • JSVerify for property-based testing.

Code Organization

This repository is structured to encourage a view of the whole repository as one application. The client and server are โ€œjustโ€ different entry points, and we use webpack to elide libraries and code that are irrelevant to a particular entry point.

There are a few key directories:

  • entry โ€“ contains the primary entry points of the application. If you want to see what happens when you start up the client or server, start there. These are also the entry points for webpack.
  • webpack contains a webpack configuration for each entry point, as well as webpack-dev-server.js which sets up the dev server used during development.
  • modules contains all of the code. Each module is a self-contained concept that may be used by other modules, command-line scripts, etc.
  • config contains configuration files for our various environments. The default config is set up as a twelve-factor app to be hosted in heroku. Most variables can be controlled via the environment โ€“ see config/default.js.
  • dist is where webpack stores compiled slices of the app.

Default modules:

  • client โ€“ React/redux front-end.
  • db โ€“ core knex database connection helpers
  • records โ€“ database record types and repositories, with base record and repository classes in record. Depends on db
  • graphql โ€“ Graphql schema and implementation. Depends on records and db
  • server โ€“ express.js server that serves the client and graphql api. Depends on graphql
  • helpers โ€“ generic helpers that can be used in any other module โ€“ no dependencies

Environment Variables

This app is set up as a 12-factor app, configurable via environment variables.

The supported environment variables are:

  • NODE_ENV โ€“ test, development, or production
  • DATABASE_URL โ€“ the url of the postgres database.
  • PORT โ€“ port for the server to bind to. Defaults to 3001
  • PUBLIC_HOST โ€“ the public facing domain name to include in e.g. links.
  • REQUIRE_SSL โ€“ if this is not false, all requests are redirected to HTTPS.
  • WEB_CONCURRENCY โ€“ # of workers to use in clustered mode. Clustering disabled if value is 1.
  • NODE_MAX_OLD_SIZE - limit node process size to a given amount. Defaults to 460 MB to work well in 512MB containers, such as heroku.
  • DEV_SERVER_DISABLE_HOST_CHECK - disables the host check in webpack dev server, to allow testing from a VM or other host.

Running locally

Run yarn dev to start up both the server and client at the same time.

yarn dev runs:

  • webpack in watch mode to hot recompile the server
  • nodemon to run the server on port 3001 and restart the server on recompilation.
  • webpack-dev-server to run the client on port 3000, with proxy through to the server
  • nodemon processes to regenerate typescript types corresponding to graphql files on change.

The dev server watches for changes and restarts express each time a dependency file changes.

The dev client is using the webpack-dev-server to hot reload the client whenever a file changes. Our webpack dev server is configured in webpack/webpack-dev-server.js.

To build for production, run:

NODE_ENV=production yarn build

This will build the entire app into ./dist/.

Tests

We are using Jest for unit testing, and colocating tests with the modules theyโ€™re testing.

Unit tests for a module are located in a __tests__ directory in the same directory as the file being tested. Tests for module.ts should be named module.test.ts. Index files should be named after their parent directory. some-module/index.ts should be tested in some-module/__tests__/module.test.ts.

Running Unit Tests

To run unit tests, run yarn jest. This simply runs jest in the current directory, which will use config in the jest section of package.json.

To run jest in watch mode, and have it automatically rerun tests when files change:

yarn jest --watch

To see other jest options, you can run:

yarn jest -- --help

yarn test:unit is an alias for running jest directly.

Component tests

We are testing react components with Enzyme . See that for more information.

These tests are included in the Jest (unit) tests (above.)

Acceptance tests

Acceptance tests are written using Nightmare. See the test:acceptence tasks for more.

Linting

We are using tslint for linting. It is run automatically before unit tests.

Property testing

We have experimentally included JSVerify for property-based testing. Property-based testing is based on generating arbitrary inputs for functions and asserting that properties are invariant across those inputs. If an input is found which violates the property, the library will automatically simplify it to the minimal case that reproduces the error.

Styleguide

We are using React Storybook to generate a styleguide for our react components.

You can run the style guide with yarn dev:storybook

GraphQL and Code Generation

We're generating type definitions from our graphql schema, queries, and mutations. This allows us to get static type safety between our graphql code and typescript implementations.

To enable this, we're storing all graphql code in individual .graphql files. Our build process and dev server look for these and use them to generate the appropriate type definitions.

User Authentication with SAML

https://docs.google.com/document/d/1NySKusNwZzChAxY5vnnXnfl9l4JmaGhT3hzyacviuZg/edit

Server

The file modules/graphql/schema-types.ts is generated by graphql-code-generator from schema.graphql and any other .graphql file in the graphql module.

schema-types exports interfaces for all graphql types in the schema, including e.g. Query.

For example, if we have the following schema:

type User {
  id: Int!
  name: String!
  email: String!
}

type Query {
  usersById(id: Int!): [User]!
}

schema-types.ts will contain definitions for:

  • Query โ€“ containing the return types for each query
  • UsersByIdQueryArgs โ€“ the expected arguments for the usersById query
  • User โ€“ the straightforward typescript definition for User.

Note that we make liberal use of ! in the query definition to disallow null values as appropriate. ! should not be used when the operation may fail. Graphql prefers null returns in that case in most circumstances.

To make use of these types, we import them into our modules/graphql/index.ts for our resolver definition.

In particular, we would define our usersById resolver as:

 usersById(obj: {}, args: UsersByIdQueryArgs, context: Context): Promise<Query['usersById']> {
   ...
 }

Note that we use UsersByIdQueryArgs to tell typescript that this should be consistent with the defined schema arguments. We could use an inline type or separate interface, but doing so would defeat TypeScript's ability to tell us when we change the schema that our implementation is no longer compatible.

Similarly, we define the return type to be Promise<Query['usersById']>. Query['usersById'] is TypeScript syntax that means "whatever tye type of usersById on in the Query type is". By using this type subscripting syntax, we get static validation that our resolver is compatible with our schema.

GraphQL in the client

In the client we generate types for our graphql queries and mutations.

Given a .graphql file containing the query:

query Users($foo: Int!) {
  usersById(id: $foo) {
    id
    name
  }
}

Entries will be added to modules/client/graphql-types.ts:

export interface UsersQueryVariables {
  foo: number;
}

export interface UsersQuery {
  // Returns all of the users in the system (canned results)
  usersById: Array<{
    id: number;
    name: string;
  }>;
}

The types and query can be used with Apollo by require-ing the .grahql file directly from typescript and passing it in where a query is expected. The UsersQuery

export async function fetchUsers(id: number): Promise<UsersQuery["usersById"]> {
  const vars: UsersQueryVariables = {
    foo: id,
  };
  const result = await graphqlClient.query<UsersQuery>({
    query: require("./Answer.graphql"),
    variables: vars,
  });

  return result.data.usersById;
}

Graphql building

The build:graphql task generates all type files. It:

  1. Generates modules/graphql/schema.json from the schema.graphql. This is used by subsequent steps.
  2. Generates schema-types.ts in the graphql module
  3. Generates graphql-types.ts in the client

The dev:graphql task โ€“ย which is run automatically by dev โ€“ watches for changes to any .graphql file and reruns build:graphql

Client Overview

The client has the following capabilities built-in:

  • Apollo Client for GraphQL queries/mutations.
  • Redux Saga for asynchronous workflows.
  • A small lens library for state selectors/updates.

Apollo Client is a smart graphql client with automatic caching and higher-order components for wiring presentation components to graphql queries.

Redux Sagas uses ES7 generator functions to support high level declaration and coordination of asynchronous workflows.

See modules/client/sagas/index.ts for an example redux saga which uses the apollo client to execute graphql queries.

Accessing/updating functional state in TypeScript requires a different solution from many of the common solutions in JavaScripot โ€“ immutable-helper, for example, is fundamentally untypable.

This starter-kit includes a library of functional lenses which are simple read/write helpers for accessing and updating substate of another object. A lens can be used as a function to get something out of an object, or can have .set or .update called on it to create an updated copy of an object.

The lens library is defined in modules/helpers/lenses.ts. See the tests for examples, as well as use in sagas/index.ts and reducers/index.ts.

CSS

CSS is implemented using the Trello CSS Guide naming conventions.

We are using the Bourbon stack as our CSS framework.

Organization

Instead of one monolithic stylesheet, each component should have its own styles.css which it requires in itโ€™s main module. This approach eases maintainability, as each react component has its own stylesheet, and webpack will only package up CSS for the components we actually use.

React components should be named with semantic, specific names. However, the CSS classes used for a React component should be as generic as possible.

Prefer using an existing component class name with a new mod- modifier to specify the behavior of your component versus adding a new css class per react component.

For example, it is better to have one btn class that has a different mod-foo modifier for the FooButton react component, than make a foo-button class.

See the Trello CSS guide for more info.

DB

The database is postgres and is preconfigured to run in Docker.

To connect via a postgres client:

  • Host: 127.0.0.1
  • Port: 5432
  • Username: root
  • No Password

Database names:

  • development

  • test

  • To start: docker-compose up and leave running

  • To create dev/test databases: yarn db:create

  • To run psql shell against development DB: yarn db:run -- development

CI

The project is configured to run in CircleCI (see .circleci/config.yml). It's possible to run locally with circleci local execute. Warning: the tests may hang after completion if there aren't enough concurrent workers. Fix this by cranking up your cores in Docker or by specifying Jest's --maxWorkers. (We'd prefer not to check in a maxWorkers setting; we want test concurrency to be elastic with the number of hardware threads available.)

More Repositories

1

objection

A lightweight dependency injection framework for Objective-C
Objective-C
1,847
star
2

heatshrink

data compression library for embedded/real-time systems
C
1,317
star
3

odo

an atomic odometer for the command line
C
71
star
4

elegant-form-validation-react

Elegant Form Validation in React
JavaScript
62
star
5

lenses

TypeScript
57
star
6

piece_pipe

PiecePipe helps you break your code into small interesting pieces and provides the glue for pipelining them together to provide elegant, readable code.
Ruby
53
star
7

VMWareFB_OpenStep

VMWareFB OpenStep display driver -- run OpenStep and NeXTStep in VMWare in high color and high res
Objective-C
42
star
8

hamsterdam

Immutable Struct-like record structures based on Hamster.
Ruby
36
star
9

hex_string

String extensions to convert binary data to / from human readable hex tuples.
Ruby
35
star
10

ecs-deployment

Shell
32
star
11

monadt

Monads & ADTs in Ruby
Ruby
27
star
12

motion-objection

Wrapping Objection in RubyMotion.
Ruby
25
star
13

publisher

Simple event firing/subscribing mechanism in Ruby.
Ruby
23
star
14

space-battle-2

RTS game engine for Atomic Games
Ruby
23
star
15

ddc

Data Driven Controllers for your Rails app
Ruby
22
star
16

ansible-laptop-playbook-example

example Ansible playbook for a laptop
Shell
21
star
17

ts-workshop

Guided TypeScript workshop
TypeScript
18
star
18

typescript-permissions-demo

TypeScript
18
star
19

jquery.expand

Structural template engine for jQuery.
CoffeeScript
17
star
20

to_api

Instead of to_xml and as_json, simply implement to_api. Return an unformatted hash. Let the model understand the data. Let the controller understand the format. In Rails, the controller can simply call render :xml => model.to_api or render :json => model.to_api.
Ruby
16
star
21

docker-cli-distribution

Example code to accompany the blog post
Ruby
14
star
22

constructor

Declarative named-argument object initialization
Ruby
13
star
23

atomic_vim

Shared Atomic Object vim configuration
Vim Script
12
star
24

autopsy

Emit helpful artifacts on Capybara Webkit test failures
Ruby
11
star
25

fire_poll

Simple method for knowing when something is ready, running correctly, and dealing with asynchronous effects in tests and specs.
Ruby
11
star
26

multiple_return_in_c_benchmarks

benchmarks for post "Comparing the Cost of Different Multiple-return Techniques in C"
C
7
star
27

utf8_to_gsm

`Utf8ToGsm` provides functionality to convert UTF-8 characters to their GSM equivalents.
Ruby
6
star
28

CSharpMemoizer

C# Memoization Example
C#
6
star
29

ts-stack

TypeScript
6
star
30

Ensemble-Test-Framework

Ensemble Test Framework
6
star
31

birdbath

A rails gem for verifying migrations with rspec or Test::Unit
Ruby
6
star
32

strange-loop-ts-workshop

Strange Loop 2019 TypeScript Workshop!
TypeScript
5
star
33

rubymotion-nimbus-table-example

Example of using a Nimbus table view in RubyMotion.
Ruby
5
star
34

ssl_certificate_expiration_checker

Simple application to check the expiration dates on SSL certificates, and send notifications to a service.
Ruby
4
star
35

mvccontrib

Atomic MVC Contrib 'Fork'
JavaScript
3
star
36

injection

Injection is a simple dependency injection plugin for Rails. It allows you to inject objects into your controllers and observers which have been described in a yaml file (config/objects.yml).
Ruby
3
star
37

csharp_mvp_generator

Ruby code that generates model / view / presenter C# code stubs
Ruby
3
star
38

diy

Constructor-based dependency injection container using YAML input.
Ruby
2
star
39

environment_configurable

A library that makes environment dependent configuration easy in Rails.
Ruby
2
star
40

roommate-lambda

AO Conference Room IoT Project - Backend
F#
2
star
41

survival-pack

Surviving a Zombie Apocolypse
2
star
42

psinfo

psinfo example application used for embedded TDD workshops
Ruby
2
star
43

roommate

AO Conference Room IoT Project - embedded device
C
2
star
44

mongoid_class_for_collection

Flexible migrations for mongoid_rails_migrations
Ruby
2
star
45

jquery.persistedtoggle

Toggle the visibility of elements on a page using keyboard shortcuts, persisting the visibility state across requests.
JavaScript
2
star
46

terraform-aws-tailscale-router

deploys a Tailscale subnet router container into an AWS VPC
HCL
2
star
47

hardmock

A strict, ordered, expectation-oriented mock object library for testing Ruby code.
Ruby
2
star
48

kinetic-c

Seagate Kinetic Cloud Storage C Client Library and Examples
C
1
star
49

june_2012_embedded_tdd_workshop

Examples and documentation from the June 2012 Embedded TDD workshop
Ruby
1
star
50

ubuntu-1604-ruby-233-nodejs-6-passenger

Docker build configuration for Ubuntu 16.04 with Ruby 2.3.3 and NodeJS 6.10.2 LTS and Passenger (specific version tags)
Dockerfile
1
star
51

require_options

Enforces key requirements in a hash
Ruby
1
star
52

angular-pdf-library-test

TypeScript
1
star
53

alexa-atomic-spin

Alexa Skill for reading Atomic Spin posts
Ruby
1
star
54

kinetic-ruby

Seagate Kinetic Protocol Client Library
Ruby
1
star
55

LTIB-Ruby-Source

Ruby package for cross-compiling with LTIB
Ruby
1
star
56

systir

Write and run automated system tests in a domain specific language (DSL) you design, as you need it.
Ruby
1
star
57

cdcicd

Test CI/CD github actions
HTML
1
star