Building a React/Redux App with JSON Web Token (JWT) Authentication

Many people have written to me asking for the client-side follow up to the JWT authentication API we built (view the tutorial on building the authentication part of the API here, and view the tutorial on creating a real time chat API here). Today, we are finally going to build it. I have recently found myself very busy between my personal projects, consulting work, and having just gotten married a little over a month ago, but I want to take a moment in this post to talk about where I want to take this tutorial series.

So far, we have built an API that will allow users to register, login (and receive a JWT to authenticate future requests), and chat. Today, we will start our React/Redux front-end that will allow us to authenticate. This tutorial will assume some basic knowledge of React and Redux. I do intend to write some simpler React/Redux tutorials for beginners later on, but for now, I needed to get this tutorial out to continue this series. Moving forward, I hope (and plan) to cover:

  • Building the client-side part of our real time chat
  • Creating both server and client-side tutorials for integrating paid memberships by using the Stripe API
  • Writing tests for both the server and client-side apps
  • Preparing our apps for production and deploying them to DigitalOcean (or something similar)

Down the road, I also hope to cover some business topics (fun fact: I studied finance in college) in order to share some ideas on turning a functional web application, or at least a minimum viable product, into a full-fledged business. If there are other topics you would like me to cover, please share that in the comments below. If you find an opportunity to improve any of the code in these tutorials, don't hesitate to let me know. This is all about learning, after all. With all that in mind, let's get started on today's topic.

Step 1: Setting Up Webpack

Note: I'm going to move quickly through some of this setup work. If you want to learn more about it, feel free to look into it. The primary focus of this tutorial is getting authentication working on the client-side.

If you are following along with this tutorial series, you have already created a client folder in your root project directory. Now would be a great time to open up that folder.

There is a reason this tweet rings true for so many, and you are about to learn why:

Setting up Webpack for the first time without following a guide can be a huge pain. I recommend starting a seed project for yourself like I have with my MERN starter repository to avoid the pain of starting up a modern JavaScript project moving forward. Please also use my seed project as a reference if you get lost in the tutorial. Okay, we need to install dev dependencies:

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0 css-loader extract-text-webpack-plugin node-sass sass-loader style-loader webpack webpack-dev-server  

We installed Webpack and Webpack Dev Server (which will serve our app and watch for changes while we're working locally). We also installed Babel and some presets for it, which will allow us to transpile the ES6 and JSX (React's special format) to plain, vanilla JavaScript so browsers can interpret it. We also installed some loaders for styling, which we'll get to in a bit.

We will need to set Babel up. We can do this by creating a new file, .babelrc in the client directory. Add the following to that file:

{
  "presets": ["react", "es2015", "stage-0"]
}

Now, we can work on our Webpack config. There are numerous ways to set this up. Feel free to Google around on this. You can create separate files for development environments and production if you wish. For simplicity's sake in this tutorial, I will create one file, webpack.config.js:

const webpack = require('webpack');  
const ExtractTextPlugin = require('extract-text-webpack-plugin');

const config = {  
  context: __dirname,
  entry: './src/index.js',
  output: {
    path: __dirname,
    filename: 'bundle.js'
  },
  module: {
    loaders: [{
      exclude: /node_modules/,
      test: /\.(js|jsx)$/,
      loader: 'babel'
    },
    {
      test: /\.scss$/,
      loader: ExtractTextPlugin.extract('css!sass')
    }]
  },
  devServer: {
    historyApiFallback: true,
    contentBase: './'
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env':{ 'NODE_ENV': JSON.stringify('production') } }),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      compress: { warnings: false },
      output: {comments: false },
      mangle: false,
      sourcemap: false,
      minimize: true,
      mangle: { except: ['$super', '$', 'exports', 'require', '$q', '$ocLazyLoad'] }
    }),
    new ExtractTextPlugin('src/public/stylesheets/app.css', {
      allChunks: true
    })
  ]
};

module.exports = config;  

There are plenty of tutorials out there that will take you much more in-depth on Webpack. If you want to understand each line, I recommend reading some of the documentation and other tutorials on it. Otherwise, let's keep moving forward.

Step 2: React/Redux Boilerplate

Now we need to install more packages:

npm install --save react react-cookie react-router react-dom react-redux redux redux-form@6.0.0-rc.3 redux-thunk  

Finally, I recommend adding a "start" script to your package.json file for convenience.

"scripts": {
    "start": "webpack-dev-server --inline --hot"
}

Note: If more keys follow that block, be sure to append a comma.

Now we have to create a pretty simple index.html so we have content to serve and a place to render our React app. I am going to use Bootstrap for simplicity's sake, but feel free to use Materialize, Foundation, or whatever other framework you would like to use instead. At the bare minimum, make sure you include the import for bundle.js, app.css, and the .wrapper div from the code below:

<!DOCTYPE html>  
<html>  
  <head>
    <meta charset="utf-8">
    <title>MERN Starter</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Import Bootstrap CSS from CDN for easy replacement/removal -->
    <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">

    <!-- Import bundled/compiled CSS -->
    <link rel="stylesheet" type="text/css" href="/src/public/stylesheets/app.css">
  </head>
  <body>
    <div class="wrapper"></div>
  </body>

  <!-- Import jQuery for Bootstrap -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>

  <!-- Import Bootstrap JS from CDN for easy replacement/removal -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>

  <!-- Import bundled JavaScript -->
  <script src="/bundle.js"></script>
</html>  

That's the last of the files we will need to create in the root client folder. Now, let's enter the client/src directory.

Create index.js and enter the following:

import React from 'react';  
import ReactDOM from 'react-dom';  
import { Provider } from 'react-redux';  
import { createStore, applyMiddleware } from 'redux';  
import { Router, browserHistory } from 'react-router';  
import reduxThunk from 'redux-thunk';  
import routes from './routes';  
import reducers from './reducers/index';  
import { AUTH_USER } from './actions/types';

// Import stylesheets like this, if you choose: import './public/stylesheets/base.scss';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);  
const store = createStoreWithMiddleware(reducers);

ReactDOM.render(  
  <Provider store={store}>
    <Router history={browserHistory} routes={routes} />
  </Provider>,
  document.querySelector('.wrapper'));

The imports should be pretty self-explanatory. We haven't created all the non-vendor files we're importing here yet, so we won't worry about those just yet.

You're probably familiar with the concept of middleware by this point from building the API that this front end will consume. Our createStoreWithMiddleware is allowing us to set up Redux Thunk as a middleware and tell Redux to use our combined reducers as the store of state for our whole application. We'll care more about that when we start building our action creators. All you need to know about the Provider portion right now is that it essentially connects your React views with your Redux stores. React is great at holding state at a component level, but for bigger, more complicated apps, Redux can help contain and notify relevant components of updates in your application's state. It all sounds convoluted at first, but as we create this app, things should start to clear up.

Moving down, we're telling ReactDOM to render Router, which we pass our routes into. The routes we create and pass in as a property to Router are what map out which path should render any given component. For example, if a visitor goes to yourapp.com/dashboard, they should see the dashboard component. This magic is in part accomplished by browserHistory, which listens for changes in the address bar, parses the URL, and renders the appropriate component. Continuing to the second argument of ReactDOM.render(), we are passing in the element we want React to attach our app to. In this case, our app will attach to the div in our index.html file with the wrapper class.

Next, let's write out some simple routes before we get started on building our components, action creators, and reducers.

Step 3: Defining Routes

Create and open src/routes.js. Add the following code:

import React from 'react';  
import { Route, IndexRoute } from 'react-router';

import App from './components/app';  
import NotFoundPage from './components/pages/not-found-page';

import HomePage from './components/pages/home-page';  
import Register from './components/auth/register';  
import Login from './components/auth/login';  
import Dashboard from './components/dashboard';  
import RequireAuth from './components/auth/require-auth';

export default (  
  <Route path="/" component={App}>
    <IndexRoute component={HomePage} />
    <Route path="register" component={Register} />
    <Route path="login" component={Login} />
    <Route path="dashboard" component={Dashboard} />

    <Route path="*" component={NotFoundPage} />
  </Route>
);

Here, we are bringing in React Router, which will handle navigation between our different components. Specifically, we're importing Route and IndexRoute, which allow us to actually define our routes. Then we import the components we will be building, and finally, we export our route definitions. We are starting out by setting the root route ("/") to the App component. This component will allow us to create sort of a layout that the other components will fit in. We will, for example, add a header and footer to this file. They will appear when visiting any of the nested routes. The IndexRoute is the main component that will display when visiting the root route, but it will also be wrapped in the App component's header and footer that we will be building. Next, we defined all of our other routes and set up a catch all, which we will create a little 404 page for to let users know they have reached a page that does not exist.

Step 4: Writing Components

Next, let's create our src/components/app.js file. Add the following:

import React, { Component } from 'react';

class App extends Component {  
  render() {
    return (
      <div>
      <p>Header here</p>

      <div className="container">
        {this.props.children}
      </div>

      <p>Footer here</p>
      </div>
    );
  }
}

export default App;  

Congratulations! That's the first React component of our app. There are a few key things to point out. Firstly, we imported React itself, and Component from React. Next, we put the App component together by extending the Component class. Finally, we wrap {this.props.children} in a container div. This is where our components from the routes will be placed. On the root route, this is where our HomePage component will load, for example. The "Header/Footer here" text will appear on every route nested within the App route definition.

I mentioned above that I'm going to zoom through the basic setup. If you're looking at the code we have written so far and wondering what's going on, please refer to other tutorials to learn the basics of React and Redux.

Continuing, let's create src/components/dashboard.js:

import React, { Component } from 'react';  
import { connect } from 'react-redux';  
import * as actions from '../actions';

class Dashboard extends Component {

  constructor(props) {
    super(props);

    this.props.protectedTest();
  }

  renderContent() {
    if(this.props.content) {
      return (
        <p>{this.props.content}</p>
      );
    }
  }

  render() {
    return (
      <div>
        {this.renderContent()}
      </div>
    );
  }
}

function mapStateToProps(state) {  
  return { content: state.auth.content };
}

export default connect(mapStateToProps, actions)(Dashboard);  

In this snippet, there are several key things to mention and come to understand. We are using connect to connect our React component to our Redux store. By encapsulating our router, and therefore contained components, within the Provider, we made our Redux store available to be connected. Our mapStateToProps function is how we subscribe our component to specific state updates from our Redux store. In that function, we are making state.auth.content accessible to our component at this.props.content. We are also injecting our action creators. In this instance, we injected all of our action creators, but you can inject select action creators by calling them specifically (e.g., { protectedTest } in this instance). To better explain what's happening in this component, without having built our action creators or reducers, let's start in our constructor. We are calling our protectedTest() function to run when the component is first called. This function (which we will soon write) will send an HTTP GET request to our API's protected test route. We wrote a function, renderContent(), which will return this.props.content if it exists, otherwise it will do nothing. We mapped state.auth.content (a piece of our Redux state) to this.props.content. After our protectedTest() request returns, it will dispatch the payload, or response, from that request to our auth reducer, which will map the response to the appropriate piece of state and send back the updated state. Our component, which is subscribed to updates in this piece of state, will then be updated with the new state.

Now, before we put together our action creators and reducer, we're going to throw together a 404 page and home page really quickly. This will do for our src/components/pages/not-found-page.js now:

import React, { Component } from 'react';

class NotFoundPage extends Component {

  render() {
    return (
      <div>
        <h1>404 - Page Not Found</h1>
        <p>I'm sorry, the page you were looking for cannot be found!</p>
      </div>
    )
  }
}
export default NotFoundPage;  

Once again, this page will display any time a user attempts to visit an undefined route. Now for our src/components/pages/home-page.js file:

import React, { Component } from 'react';

class HomePage extends Component {  
  render() {
    return (
      <div>Hello world! This is the home page route.</div>
    );
  }
}

export default HomePage;  

Step 5: Action Creators

Now we can dive in and make our action creators. Create and open src/actions/types.js:

export const AUTH_USER = 'auth_user',  
             UNAUTH_USER = 'unauth_user',
             AUTH_ERROR = 'auth_error',
             FORGOT_PASSWORD_REQUEST = 'forgot_password_request',
             RESET_PASSWORD_REQUEST = 'reset_password_request',
             PROTECTED_TEST = 'protected_test';

These will be the "types" of the actions you dispatch to your reducers. These will be run through a switch statement in your reducer to determine what state to update. Let's install one more dependency.

npm install --save axios  

Axios will allow us to make HTTP requests. There are several other libraries available for this (a lot of people like Fetch, for example).
Now, create src/actions/index.js:

import axios from 'axios';  
import { browserHistory } from 'react-router';  
import cookie from 'react-cookie';  
import { AUTH_USER,  
         AUTH_ERROR,
         UNAUTH_USER,
         PROTECTED_TEST } from './types';

const API_URL = 'http://localhost:3000/api';

export function errorHandler(dispatch, error, type) {  
  let errorMessage = '';

  if(error.data.error) {
    errorMessage = error.data.error;
  } else if(error.data{
    errorMessage = error.data;
  } else {
    errorMessage = error;
  }

  if(error.status === 401) {
    dispatch({
      type: type,
      payload: 'You are not authorized to do this. Please login and try again.'
    });
    logoutUser();
  } else {
    dispatch({
      type: type,
      payload: errorMessage
    });
  }
}

export function loginUser({ email, password }) {  
  return function(dispatch) {
    axios.post(`${API_URL}/auth/login`, { email, password })
    .then(response => {
      cookie.save('token', response.data.token, { path: '/' });
      dispatch({ type: AUTH_USER });
      window.location.href = CLIENT_ROOT_URL + '/dashboard';
    })
    .catch((error) => {
      errorHandler(dispatch, error.response, AUTH_ERROR)
    });
    }
  }

export function registerUser({ email, firstName, lastName, password }) {  
  return function(dispatch) {
    axios.post(`${API_URL}/auth/register`, { email, firstName, lastName, password })
    .then(response => {
      cookie.save('token', response.data.token, { path: '/' });
      dispatch({ type: AUTH_USER });
      window.location.href = CLIENT_ROOT_URL + '/dashboard';
    })
    .catch((error) => {
      errorHandler(dispatch, error.response, AUTH_ERROR)
    });
  }
}

export function logoutUser() {  
  return function (dispatch) {
    dispatch({ type: UNAUTH_USER });
    cookie.remove('token', { path: '/' });

    window.location.href = CLIENT_ROOT_URL + '/login';
  }
}

export function protectedTest() {  
  return function(dispatch) {
    axios.get(`${API_URL}/protected`, {
      headers: { 'Authorization': cookie.load('token') }
    })
    .then(response => {
      dispatch({
        type: PROTECTED_TEST,
        payload: response.data.content
      });
    })
    .catch((error) => {
      errorHandler(dispatch, error.response, AUTH_ERROR)
    });
  }
}

This can look daunting at first, but once broken down, it makes a lot of sense. We set up an error handler, which will dispatch our authentication error messages to our store and even log our user out on a 401 response (unauthorized). Since most HTTP requests are structured similarly, I'm just going to break two of them down for demonstration. Our registerUser() action creator takes an object as an argument. If you're not familiar with this syntax, just note it is identical to:

{ email: email, firstName: firstName, lastName: lastName, password: password }

We just used some slightly cleaner ES6. We are using axios to send a POST request to our API, with the request body being the form inputs (in object form). Since axios is promise-based, we have access to .then() in the result of a successful response and .catch() in the result of an error. We tie our errorHandler to the .catch(), and we save the JSON Web Token response we get from our API in a cookie in the event that we have a successful request and reach our .then(). We also dispatch an action with the type AUTH_USER, which will tell our reducer to update our state, saying the user is now authenticated. Then we redirect the user to the dashboard.

Our protectedTest() action creator is sending a GET request to our API's /protected endpoint, which requires a valid JWT to be sent in the authorization header in order to send back a response. Note how we send the authorization header with this request. Additionally, the response is being dispatched in the payload. Keep in mind, our protected route is sending a JSON response. The key we set it up to send was content. You will be able to access your API responses in other requests in the same way (response.data.yourKeyName).

Note: In the event of a POST request that needs to send headers, the headers are the third argument, behind the data being sent in the body of the request.

Step 6: Reducers

Next, let's move over to our reducers. We're getting closer to done, so hang in there. Create src/reducers/auth_reducer.js:

import { AUTH_USER,  
         UNAUTH_USER,
         AUTH_ERROR,
         PROTECTED_TEST } from '../actions/types';

const INITIAL_STATE = { error: '', message: '', content: '', authenticated: false}

export default function (state = INITIAL_STATE, action) {  
  switch(action.type) {
    case AUTH_USER:
      return { ...state, error: '', message: '', authenticated: true };
    case UNAUTH_USER:
      return { ...state, authenticated: false };
    case AUTH_ERROR:
      return { ...state, error: action.payload };
    case PROTECTED_TEST:
      return { ...state, content: action.payload };
  }

  return state;
}

As you can see, when our actions are dispatched to our store, they are using the "type" to determine which pieces of state will be updated and how they will be updated. For example, when our protectedTest() action creator dispatches the type PROTECTED_TEST, our reducer knows to change content to the payload we set (in this case, response.data.content from our HTTP request).

Next, we will need to set up a "root reducer" by combining our reducers to create one, comprehensive store. We are going to get ahead a bit again here and add a second reducer for managing our forms. Create src/reducers/index.js:

import { combineReducers } from 'redux';  
import { reducer as formReducer } from 'redux-form';  
import authReducer from './auth_reducer';

const rootReducer = combineReducers({  
  auth: authReducer,
  form: formReducer
});

export default rootReducer;  

Step 7: Building the Login/Registration Forms

Now, if you take another look at src/index.js, things should be making a lot more sense. Next, we will build very simple forms for registration and login. Create src/components/auth/register.js:

import React, { Component } from 'react';  
import { connect } from 'react-redux';  
import { Field, reduxForm } from 'redux-form';  
import { registerUser } from '../../actions';

const form = reduxForm({  
  form: 'register',
  validate
});

const renderField = field => (  
    <div>
      <input className="form-control" {...field.input}/>
      {field.touched && field.error && <div className="error">{field.error}</div>}
    </div>
);

function validate(formProps) {  
  const errors = {};

  if (!formProps.firstName) {
    errors.firstName = 'Please enter a first name';
  }

  if (!formProps.lastName) {
    errors.lastName = 'Please enter a last name';
  }

  if (!formProps.email) {
    errors.email = 'Please enter an email';
  }

  if (!formProps.password) {
    errors.password = 'Please enter a password';
  }

  return errors;
}

class Register extends Component {  
  handleFormSubmit(formProps) {
    this.props.registerUser(formProps);
  }

  renderAlert() {
    if(this.props.errorMessage) {
      return (
        <div>
          <span><strong>Error!</strong> {this.props.errorMessage}</span>
        </div>
      );
    }
  }

  render() {
    const { handleSubmit } = this.props;

    return (
      <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
      {this.renderAlert()}
      <div className="row">
        <div className="col-md-6">
          <label>First Name</label>
          <Field name="firstName" className="form-control" component={renderField} type="text" />
        </div>
        <div className="col-md-6">
          <label>Last Name</label>
          <Field name="lastName" className="form-control" component={renderField} type="text" />
        </div>
      </div>
        <div className="row">
          <div className="col-md-12">
            <label>Email</label>
            <Field name="email" className="form-control" component={renderField} type="text" />
          </div>
        </div>
        <div className="row">
          <div className="col-md-12">
            <label>Password</label>
            <Field name="password" className="form-control" component={renderField} type="password" />
          </div>
        </div>
        <button type="submit" className="btn btn-primary">Register</button>
      </form>
    );
  }
}

function mapStateToProps(state) {  
  return {
    errorMessage: state.auth.error,
    message: state.auth.message
  };
}

export default connect(mapStateToProps, { registerUser })(form(Register));  

Note: Make sure you have Redux-Form version 6.0.0-rc.3 or higher, otherwise this won't work. There were large changes after React upgraded to version 15.2.0.

A lot of this should make sense. We are explicitly importing our registerUser() action creator from our actions file, and we are importing Field and reduxForm from Redux-Form, which is a great tool for building complex forms in your app. We are creating and binding some simple client-side validation into our registration form. We created a function, handleFormSubmit(), which calls our registerUser() action creator with the form inputs (formProps) as an argument, and we tell our form to call that function when the form is submitted.

Now we can create a login form, create src/components/auth/login.js:

import React, { Component } from 'react';  
import { connect } from 'react-redux';  
import { Field, reduxForm } from 'redux-form';  
import { Link } from 'react-router';  
import { loginUser } from '../../actions';

const form = reduxForm({  
  form: 'login'
});

class Login extends Component {  
  handleFormSubmit(formProps) {
    this.props.loginUser(formProps);
  }

  renderAlert() {
    if(this.props.errorMessage) {
      return (
        <div>
          <span><strong>Error!</strong> {this.props.errorMessage}</span>
        </div>
      );
    }
  }

  render() {
    const { handleSubmit } = this.props;

    return (
      <div>
        <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
        {this.renderAlert()}
          <div>
            <label>Email</label>
            <Field name="email" className="form-control" component="input" type="text" />
          </div>
          <div>
            <label>Password</label>
            <Field name="password" className="form-control" component="input" type="password" />
          </div>
          <button type="submit" className="btn btn-primary">Login</button>
        </form>
      </div>
    );
  }
}

function mapStateToProps(state) {  
  return {
    errorMessage: state.auth.error,
    message: state.auth.message
  };
}

export default connect(mapStateToProps, { loginUser })(form(Login));  

Our login page is going to call our loginUser() action creator with the inputs from our form. It is listening for an error response, which the renderAlert() function will display if an error is received.

We only have a couple more steps. At this point, you should be able to register and login, but your dashboard won't be protected, and if you refresh, your authentication state will reset, meaning your app will think you aren't logged in anymore. Let's address the route protection issue first.

Step 8: Creating a Higher Order Component

We are going to create a higher order component, which will allow us pass some functionality down to multiple other components. This component will subscribe to authentication state. If the user is not authenticated, they will be redirected to the login page. Otherwise, they will be allowed to proceed to their intended route. Create src/components/auth/require-auth.js:

import React, { Component } from 'react';  
import { connect } from 'react-redux';

export default function(ComposedComponent) {  
  class Authentication extends Component {
    static contextTypes = {
      router: React.PropTypes.object
    }

    componentWillMount() {
      if(!this.props.authenticated) {
        this.context.router.push('/login');
      }
    }

    componentWillUpdate(nextProps) {
      if(!nextProps.authenticated) {
        this.context.router.push('/login');
      }
    }

    render() {
      return <ComposedComponent {...this.props} />
    }
  }

  function mapStateToProps(state) {
    return { authenticated: state.auth.authenticated };
  }

  return connect(mapStateToProps)(Authentication);
}

Now, open src/routes.js back up. We're going to wrap this higher order component around our dashboard.

<Route path="dashboard" component={RequireAuth(Dashboard)} />  

Step 9: Finishing Touches

Finally, let's make sure our users stay authenticated even if they refresh and lose their state.

Back in src/index.js, we need to import react-cookie:

import cookie from 'react-cookie';  

We also need to check if there is a JWT present in the user's cookies. If so, this means we should update the state to an authenticated status. Do so by adding the following below where we defined the store variable:

const token = cookie.load('token');

if (token) {  
  store.dispatch({ type: AUTH_USER });
}

With all of this said and done, you should now have a working front end authentication system that will tie in perfectly with your back end. To test this, run npm start in your console. Your Webpack should start building, and once it says your bundle is valid, you can head to http://localhost:8080/register to set up a new account. You can also try going to http://localhost:8080/login to login once you've set an account up. If there are any errors, refer to the source code for my full stack JavaScript starter.

If you find areas where the code could be improved, please let me know. I'll answer as many questions as I can, and like I said, I intend to continue this tutorial series in the near future with some more exciting feature builds.

In wrapping up, I would like to thank David Meents for reviewing this tutorial before I released it into the wild.

Joshua Slate

Entrepreneur. Rock climber. Software engineer. Founder of @SlatePeak and others.

Saint Paul, MN

Subscribe to SlatePeak

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!