One place for hosting & domains

      Management

      How To Build a Customer List Management App with React and TypeScript


      The author selected the Tech Education Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      TypeScript has brought a lot of improvement into how JavaScript developers structure and write code for apps, especially web applications. Defined as a superset of JavaScript, TypeScript behaves identically to JavaScript but with extra features designed to help developers build larger and more complex programs with fewer or no bugs. TypeScript is increasingly gaining popularity; adopted by major companies like Google for the Angular web framework. The Nest.js back-end framework was also built with TypeScript.

      One of the ways to improve productivity as a developer is the ability to implement new features as quickly as possible without any concern over breaking the existing app in production. To achieve this, writing statically typed code is a style adopted by many seasoned developers. Statically typed programming languages like TypeScript enforce an association for every variable with a data type; such as a string, integer, boolean, and so on. One of the major benefits of using a statically typed programming language is that type checking is completed at compile time, therefore developers can see errors in their code at a very early stage.

      React is an open-source JavaScript library, which developers use to create high-end user interfaces for scalable web applications. The great performance and dynamic user interfaces built with React for single-page applications make it a popular choice among developers.

      In this tutorial, you will create a customer list management application with a separate REST API backend and a frontend built with React and TypeScript. You will build the backend using a fake REST API named json-server. You’ll use it to quickly set up a CRUD (Create, Read, Update, and Delete) backend. Consequently you can focus on handling the front-end logic of an application using React and TypeScript.

      Prerequisites

      To complete this tutorial, you will need:

      Step 1 — Installing TypeScript and Creating the React Application

      In this step, you will install the TypeScript package globally on your machine by using the Node Package Manager (npm). After that, you will also install React and its dependencies, and check that your React app is working by running the development server.

      To begin, open a terminal and run the following command to install TypeScript:

      • npm install -g typescript

      Once the installation process is complete, execute the following command to check your installation of TypeScript:

      You will see the current version installed on your machine:

      Output

      Version 3.4.5

      Next, you will install the React application by using the create-react-app tool to set up the application with a single command. You'll use the npx command, which is a package runner tool that comes with npm 5.2+. The create-react-app tool has built-in support for working with TypeScript without any extra configuration required. Run the following command to create and install a new React application named typescript-react-app:

      • npx create-react-app typescript-react-app --typescript

      The preceding command will create a new React application with the name typescript-react-app. The --typescript flag will set the default filetype for React components to .tsx.

      Before you complete this section, the application will require moving from one port to another. To do that, you will need to install a routing library for your React application named React Router and its corresponding TypeScript definitions. You will use yarn to install the library and other packages for this project. This is because yarn is faster, especially for installing dependencies for a React application. Move into the newly created project folder and then install React Router with the following command:

      • cd typescript-react-app
      • yarn add react-router-dom

      You now have the React Router package, which will provide the routing functionality within your project. Next, run the following command to install the TypeScript definitions for React Router:

      • yarn add @types/react-router-dom

      Now you'll install axios, which is a promised-based HTTP client for browsers, to help with the process of performing HTTP requests from the different components that you will create within the application:

      Once the installation process is complete, start the development server with:

      Your application will be running on http://localhost:3000.

      React application homepage

      You have successfully installed TypeScript, created a new React application, and installed React Router in order to help with navigating from one page of the application to another. In the next section, you will set up the back-end server for the application.

      Step 2 — Creating a JSON Server

      In this step, you'll create a mock server that your React application can quickly connect with, as well as use its resources. It is important to note that this back-end service is not suitable for an application in production. You can use Nest.js, Express, or any other back-end technology to build a RESTful API in production. json-server is a useful tool whenever you need to create a prototype and mock a back-end server.

      You can use either npm or yarn to install json-server on your machine. This will make it available from any directory of your project whenever you might need to make use of it. Open a new terminal window and run this command to install json-server while you are still within the project directory:

      • yarn global add json-server

      Next, you will create a JSON file that will contain the data that will be exposed by the REST API. For the objects specified in this file (which you'll create), a CRUD endpoint will be generated automatically. To begin, create a new folder named server and then move into it:

      Now, use nano to create and open a new file named db.json:

      Add the following content to the file:

      /server/db.json

      {
          "customers": [
              {
                  "id": 1,
                  "first_name": "Customer_1",
                  "last_name": "Customer_11",
                  "email": "customer1@mail.com",
                  "phone": "00000000000",
                  "address": "Customer_1 Address",
                  "description": "Customer_1 description"
              },
              {
                  "id": 2,
                  "first_name": "Customer_2",
                  "last_name": "Customer_2",
                  "email": "customer2@mail.com",
                  "phone": "00000000000",
                  "address": "Customer_2 Adress",
                  "description": "Customer_2 Description"
              }
          ]
      }
      

      The JSON structure consists of a customer object, which has two datasets assigned. Each customer consists of seven properties: id, description, first_name, last_name, email, phone, and address.

      Save and exit the file.

      By default, the json-server runs on port 3000—this is the same port on which your React application runs. To avoid conflict, you can change the default port for the json-server. To do that, move to the root directory of the application:

      • cd ~/typescript-react-app

      Open the application with your preferred text editor and create a new file named json-server.json:

      Now insert the following to update the port number:

      /json-server.json

      {
          "port": 5000
      }
      

      This will act as the configuration file for the json-server and it will ensure that the server runs on the port specified in it at all times.

      Save and exit the file.

      To run the server, use the following command:

      • json-server --watch server/db.json

      This will start the json-server on port 5000. If you navigate to http://localhost:5000/customers in your browser, you will see the server showing your customer list.

      Customer list shown by json-server

      To streamline the process of running the json-server, you can update package.json with a new property named server to the scripts object as shown here:

      /package.json

      {
      ...
        "scripts": {
          "start": "react-scripts start",
          "build": "react-scripts build",
          "test": "react-scripts test",
          "eject": "react-scripts eject",
          "server": "json-server --watch server/db.json"
        },
      ...
      }
      

      Save and exit the file.

      Now anytime you wish to start the json-server, all you have to do is run yarn server from the terminal.

      You've created a simple REST API that you will use as the back-end server for this application. You also created a customer JSON object that will be used as the default data for the REST API. Lastly, you configured an alternative port for the back-end server powered by json-server. Next, you will build reusable components for your application.

      Step 3 — Creating Reusable Components

      In this section, you will create the required React components for the application. This will include components to create, display, and edit the details of a particular customer in the database respectively. You'll also build some of the TypeScript interfaces for your application.

      To begin, move back to the terminal where you have the React application running and stop the development server with CTRL + C. Next, navigate to the ./src/ folder:

      Then, create a new folder named components inside of it and move into the new folder:

      • mkdir components
      • cd components

      Within the newly created folder, create a customer folder and then move into it:

      • mkdir customer
      • cd customer

      Now create two new files named Create.tsx and Edit.tsx:

      • touch Create.tsx Edit.tsx

      These files are React reusable components that will render the forms and hold all the business logic for creating and editing the details of a customer respectively.

      Open the Create.tsx file in your text editor and add the following code:

      /src/components/customer/Create.tsx

      import * as React from 'react';
      import axios from 'axios';
      import { RouteComponentProps, withRouter } from 'react-router-dom';
      
      export interface IValues {
          first_name: string,
          last_name: string,
          email: string,
          phone: string,
          address: string,
          description: string,
      }
      export interface IFormState {
          [key: string]: any;
          values: IValues[];
          submitSuccess: boolean;
          loading: boolean;
      }
      
      

      Here you've imported React, axios, and other required components necessary for routing from the React Router package. After that you created two new interfaces named IValues and IFormState. TypeScript interfaces help to define the specific type of values that should be passed to an object and enforce consistency throughout an application. This ensures that bugs are less likely to appear in your program.

      Next, you will build a Create component that extends React.Component. Add the following code to the Create.tsx file immediately after the IFormState interface:

      /src/components/customer/Create.tsx

      ...
      class Create extends React.Component<RouteComponentProps, IFormState> {
          constructor(props: RouteComponentProps) {
              super(props);
              this.state = {
                  first_name: '',
                  last_name: '',
                  email: '',
                  phone: '',
                  address: '',
                  description: '',
                  values: [],
                  loading: false,
                  submitSuccess: false,
              }
          }
      }
      export default withRouter(Create)
      

      Here you've defined a React component in Typescript. In this case, the Create class component accepts props (short for “properties”) of type RouteComponentProps and uses a state of type IFormState. Then, inside the constructor, you initialized the state object and defined all the variables that will represent the rendered values for a customer.

      Next, add these methods within the Create class component, just after the constructor. You'll use these methods to process customer forms and handle all changes in the input fields:

      /src/components/customer/Create.tsx

      ...
                values: [],
                loading: false,
                submitSuccess: false,
            }
        }
      
        private processFormSubmission = (e: React.FormEvent<HTMLFormElement>): void => {
                e.preventDefault();
                this.setState({ loading: true });
                const formData = {
                    first_name: this.state.first_name,
                    last_name: this.state.last_name,
                    email: this.state.email,
                    phone: this.state.phone,
                    address: this.state.address,
                    description: this.state.description,
                }
                this.setState({ submitSuccess: true, values: [...this.state.values, formData], loading: false });
                axios.post(`http://localhost:5000/customers`, formData).then(data => [
                    setTimeout(() => {
                        this.props.history.push('/');
                    }, 1500)
                ]);
            }
      
            private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {
                e.preventDefault();
                this.setState({
                    [e.currentTarget.name]: e.currentTarget.value,
            })
        }
      
      ...
      export default withRouter(Create)
      ...
      

      The processFormSubmission() method receives the details of the customer from the application state and posts it to the database using axios. The handleInputChanges() uses React.FormEvent to obtain the values of all input fields and calls this.setState() to update the state of the application.

      Next, add the render() method within the Create class component immediately after the handleInputchanges() method. This render() method will display the form to create a new customer in the application:

      /src/components/customer/Create.tsx

      ...
        public render() {
            const { submitSuccess, loading } = this.state;
            return (
                <div>
                    <div className={"col-md-12 form-wrapper"}>
                        <h2> Create Post </h2>
                        {!submitSuccess && (
                            <div className="alert alert-info" role="alert">
                                Fill the form below to create a new post
                        </div>
                        )}
                        {submitSuccess && (
                            <div className="alert alert-info" role="alert">
                                The form was successfully submitted!
                                </div>
                        )}
                        <form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
                            <div className="form-group col-md-12">
                                <label htmlFor="first_name"> First Name </label>
                                <input type="text" id="first_name" onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="last_name"> Last Name </label>
                                <input type="text" id="last_name" onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="email"> Email </label>
                                <input type="email" id="email" onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="phone"> Phone </label>
                                <input type="text" id="phone" onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="address"> Address </label>
                                <input type="text" id="address" onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
                            </div>
                            <div className="form-group col-md-12">
                                <label htmlFor="description"> Description </label>
                                <input type="text" id="description" onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
                            </div>
                            <div className="form-group col-md-4 pull-right">
                                <button className="btn btn-success" type="submit">
                                    Create Customer
                                </button>
                                {loading &&
                                    <span className="fa fa-circle-o-notch fa-spin" />
                                }
                            </div>
                        </form>
                    </div>
                </div>
            )
        }
      ...
      

      Here, you created a form with the input fields to hold the values of the first_name, last_name, email, phone, address, and description of a customer. Each of the input fields have a method handleInputChanges() that runs on every keystroke, updating the React state with the value it obtains from the input field. Furthermore, depending on the state of the application, a boolean variable named submitSuccess will control the message that the application will display before and after creating a new customer.

      You can see the complete code for this file in this GitHub repository.

      Save and exit Create.tsx.

      Now that you have added the appropriate logic to the Create component file for the application, you'll proceed to add contents for the Edit component file.

      Open your Edit.tsx file within the customer folder, and start by adding the following content to import React, axios, and also define TypeScript interfaces:

      /src/components/customer/Edit.tsx

      import * as React from 'react';
      import { RouteComponentProps, withRouter } from 'react-router-dom';
      import axios from 'axios';
      
      export interface IValues {
          [key: string]: any;
      }
      export interface IFormState {
          id: number,
          customer: any;
          values: IValues[];
          submitSuccess: boolean;
          loading: boolean;
      }
      

      Similarly to the Create component, you import the required modules and create IValues and IFormState interfaces respectively. The IValues interface defines the data type for the input fields' values, while you'll use IFormState to declare the expected type for the state object of the application.

      Next, create the EditCustomer class component directly after the IFormState interface block as shown here:

      /src/components/customer/Edit.tsx

      ...
      class EditCustomer extends React.Component<RouteComponentProps<any>, IFormState> {
          constructor(props: RouteComponentProps) {
              super(props);
              this.state = {
                  id: this.props.match.params.id,
                  customer: {},
                  values: [],
                  loading: false,
                  submitSuccess: false,
              }
          }
      }
      export default withRouter(EditCustomer)
      

      This component takes the RouteComponentProps<any> and an interface of IFormState as a parameter. You use the addition of <any> to the RouteComponentProps because whenever React Router parses path parameters, it doesn’t do any type conversion to ascertain whether the type of the data is number or string. Since you're expecting a parameter for uniqueId of a customer, it is safer to use any.

      Now add the following methods within the component:

      /src/components/customer/Edit.tsx

      ...
          public componentDidMount(): void {
              axios.get(`http://localhost:5000/customers/${this.state.id}`).then(data => {
                  this.setState({ customer: data.data });
              })
          }
      
          private processFormSubmission = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
              e.preventDefault();
              this.setState({ loading: true });
              axios.patch(`http://localhost:5000/customers/${this.state.id}`, this.state.values).then(data => {
                  this.setState({ submitSuccess: true, loading: false })
                  setTimeout(() => {
                      this.props.history.push('/');
                  }, 1500)
              })
          }
      
          private setValues = (values: IValues) => {
              this.setState({ values: { ...this.state.values, ...values } });
          }
          private handleInputChanges = (e: React.FormEvent<HTMLInputElement>) => {
              e.preventDefault();
              this.setValues({ [e.currentTarget.id]: e.currentTarget.value })
          }
      ...
      }
      
      export default withRouter(EditCustomer)
      

      First, you add a componentDidMount() method, which is a lifecycle method that is being called when the component is created. The method takes the id obtained from the route parameter to identify a particular customer as a parameter, uses it to retrieve their details from the database and then populates the form with it. Furthermore, you add methods to process form submission and handle changes made to the values of the input fields.

      Lastly, add the render() method for the Edit component:

      /src/components/customer/Edit.tsx

      ...
          public render() {
              const { submitSuccess, loading } = this.state;
              return (
                  <div className="App">
                      {this.state.customer &&
                          <div>
                              < h1 > Customer List Management App</h1>
                              <p> Built with React.js and TypeScript </p>
      
                              <div>
                                  <div className={"col-md-12 form-wrapper"}>
                                      <h2> Edit Customer </h2>
                                      {submitSuccess && (
                                          <div className="alert alert-info" role="alert">
                                              Customer's details has been edited successfully </div>
                                      )}
                                      <form id={"create-post-form"} onSubmit={this.processFormSubmission} noValidate={true}>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="first_name"> First Name </label>
                                              <input type="text" id="first_name" defaultValue={this.state.customer.first_name} onChange={(e) => this.handleInputChanges(e)} name="first_name" className="form-control" placeholder="Enter customer's first name" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="last_name"> Last Name </label>
                                              <input type="text" id="last_name" defaultValue={this.state.customer.last_name} onChange={(e) => this.handleInputChanges(e)} name="last_name" className="form-control" placeholder="Enter customer's last name" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="email"> Email </label>
                                              <input type="email" id="email" defaultValue={this.state.customer.email} onChange={(e) => this.handleInputChanges(e)} name="email" className="form-control" placeholder="Enter customer's email address" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="phone"> Phone </label>
                                              <input type="text" id="phone" defaultValue={this.state.customer.phone} onChange={(e) => this.handleInputChanges(e)} name="phone" className="form-control" placeholder="Enter customer's phone number" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="address"> Address </label>
                                              <input type="text" id="address" defaultValue={this.state.customer.address} onChange={(e) => this.handleInputChanges(e)} name="address" className="form-control" placeholder="Enter customer's address" />
                                          </div>
                                          <div className="form-group col-md-12">
                                              <label htmlFor="description"> Description </label>
                                              <input type="text" id="description" defaultValue={this.state.customer.description} onChange={(e) => this.handleInputChanges(e)} name="description" className="form-control" placeholder="Enter Description" />
                                          </div>
                                          <div className="form-group col-md-4 pull-right">
                                              <button className="btn btn-success" type="submit">
                                                  Edit Customer </button>
                                              {loading &&
                                                  <span className="fa fa-circle-o-notch fa-spin" />
                                              }
                                          </div>
                                      </form>
                                  </div>
                              </div>
                          </div>
                      }
                  </div>
              )
          }
      ...    
      

      Here, you created a form to edit the details of a particular customer, and then populated the input fields within that form with the customer's details that your application's state obtained. Similarly to the Create component, changes made to all the input fields will be handled by the handleInputChanges() method.

      You can see the complete code for this file in this GitHub repository.

      Save and exit Edit.tsx.

      To view the complete list of customers created within the application, you’ll create a new component within the ./src/components folder and name it Home.tsx:

      • cd ./src/components
      • nano Home.tsx

      Add the following content:

      /src/components/Home.tsx

      import * as React from 'react';
      import { Link, RouteComponentProps } from 'react-router-dom';
      import axios from 'axios';
      
      interface IState {
          customers: any[];
      }
      
      export default class Home extends React.Component<RouteComponentProps, IState> {
          constructor(props: RouteComponentProps) {
              super(props);
              this.state = { customers: [] }
          }
          public componentDidMount(): void {
              axios.get(`http://localhost:5000/customers`).then(data => {
                  this.setState({ customers: data.data })
              })
          }
          public deleteCustomer(id: number) {
              axios.delete(`http://localhost:5000/customers/${id}`).then(data => {
                  const index = this.state.customers.findIndex(customer => customer.id === id);
                  this.state.customers.splice(index, 1);
                  this.props.history.push('/');
              })
          }
      }
      

      Here, you've imported React, axios, and other required components from React Router. You created two new methods within the Home component:

      • componentDidMount(): The application invokes this method immediately after a component is mounted. Its responsibility here is to retrieve the list of customers and update the home page with it.
      • deleteCustomer(): This method will accept an id as a parameter and will delete the details of the customer identified with that id from the database.

      Now add the render() method to display the table that holds the list of customers for the Home component:

      /src/components/Home.tsx

      ...
      public render() {
              const customers = this.state.customers;
              return (
                  <div>
                      {customers.length === 0 && (
                          <div className="text-center">
                              <h2>No customer found at the moment</h2>
                          </div>
                      )}
                      <div className="container">
                          <div className="row">
                              <table className="table table-bordered">
                                  <thead className="thead-light">
                                      <tr>
                                          <th scope="col">Firstname</th>
                                          <th scope="col">Lastname</th>
                                          <th scope="col">Email</th>
                                          <th scope="col">Phone</th>
                                          <th scope="col">Address</th>
                                          <th scope="col">Description</th>
                                          <th scope="col">Actions</th>
                                      </tr>
                                  </thead>
                                  <tbody>
                                      {customers && customers.map(customer =>
                                          <tr key={customer.id}>
                                              <td>{customer.first_name}</td>
                                              <td>{customer.last_name}</td>
                                              <td>{customer.email}</td>
                                              <td>{customer.phone}</td>
                                              <td>{customer.address}</td>
                                              <td>{customer.description}</td>
                                              <td>
                                                  <div className="d-flex justify-content-between align-items-center">
                                                      <div className="btn-group" style={{ marginBottom: "20px" }}>
                                                          <Link to={`edit/${customer.id}`} className="btn btn-sm btn-outline-secondary">Edit Customer </Link>
                                                          <button className="btn btn-sm btn-outline-secondary" onClick={() => this.deleteCustomer(customer.id)}>Delete Customer</button>
                                                      </div>
                                                  </div>
                                              </td>
                                          </tr>
                                      )}
                                  </tbody>
                              </table>
                          </div>
                      </div>
                  </div>
              )
          }
      ...
      

      In this code block, you retrieve the lists of customers from the application's state as an array, iterate over it, and display it within an HTML table. You also add the customer.id parameter, which the method uses to identify and delete the details of a particular customer from the list.

      Save and exit Home.tsx.

      You've adopted a statically typed principle for all the components created with this application by defining types for the components and props through the use of interfaces. This is one of the best approaches to using TypeScript for a React application.

      With this, you've finished creating all the required reusable components for the application. You can now update the app component with links to all the components that you have created so far.

      Step 4 — Setting Up Routing and Updating the Entry Point of the Application

      In this step, you will import the necessary components from the React Router package and configure the App component to render different components depending on the route that is loaded. This will allow you to navigate through different pages of the application. Once a user visits a route, for example /create, React Router will use the path specified to render the contents and logic within the appropriate component defined to handle such route.

      Navigate to ./src/App.tsx:

      Then replace its content with the following:

      /src/App.tsx

      import * as React from 'react';
      import './App.css';
      import { Switch, Route, withRouter, RouteComponentProps, Link } from 'react-router-dom';
      import Home from './components/Home';
      import Create from './components/customer/Create';
      import EditCustomer from './components/customer/Edit';
      
      class App extends React.Component<RouteComponentProps<any>> {
        public render() {
          return (
            <div>
              <nav>
                <ul>
                  <li>
                    <Link to={'/'}> Home </Link>
                  </li>
                  <li>
                    <Link to={'/create'}> Create Customer </Link>
                  </li>
                </ul>
              </nav>
              <Switch>
                <Route path={'/'} exact component={Home} />
                <Route path={'/create'} exact component={Create} />
                <Route path={'/edit/:id'} exact component={EditCustomer} />
              </Switch>
            </div>
          );
        }
      }
      export default withRouter(App);
      

      You imported all the necessary components from the React Router package and you also imported the reusable components for creating, editing, and viewing customers' details.

      Save and exit App.tsx.

      The ./src/index.tsx file is the entry point for this application and renders the application. Open this file and import React Router into it, then wrap the App component inside a BrowserRouter:

      /src/index.tsx

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      import App from './App';
      import { BrowserRouter } from 'react-router-dom'; 
      import * as serviceWorker from './serviceWorker';
      ReactDOM.render(
          <BrowserRouter>
              <App />
          </BrowserRouter>
          , document.getElementById('root')
      );
      serviceWorker.unregister();
      

      React Router uses the BrowserRouter component to make your application aware of the navigation, such as history and current path.

      Once you've finished editing Index.tsx, save and exit.

      Lastly, you will use Bootstrap to add some style to your application. Bootstrap is a popular HTML, CSS, and JavaScript framework for developing responsive, mobile-first projects on the web. It allows developers to build an appealing user interface without having to write too much CSS. It comes with a responsive grid system that gives a web page a finished look that works on all devices.

      To include Bootstrap and styling for your application, replace the contents of ./src/App.css with the following:

      /src/App.css

      @import 'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css';
      
      .form-wrapper {
        width: 500px;
        margin: 0 auto;
      }
      .App {
        text-align: center;
        margin-top: 30px;
      }
      nav {
        width: 300px;
        margin: 0 auto;
        background: #282c34;
        height: 70px;
        line-height: 70px;
      }
      nav ul li {
        display: inline;
        list-style-type: none;
        text-align: center;
        padding: 30px;
      }
      nav ul li a {
        margin: 50px 0;
        font-weight: bold;
        color: white;
        text-decoration: none;
      }
      nav ul li a:hover {
        color: white;
        text-decoration: none;
      }
      table {
        margin-top: 50px;
      }
      .App-link {
        color: #61dafb;
      }
      @keyframes App-logo-spin {
        from {
          transform: rotate(0deg);
        }
        to {
          transform: rotate(360deg);
        }
      }
      

      You have used Bootstrap here to enhance the look and feel of the application by giving it a default layout, styles, and color. You have also added some custom styles, particularly to the navigation bar.

      Save and exit App.css.

      In this section, you have configured React Router to render the appropriate component depending on the route visited by the user and also added some styling to make the application more attractive to users. Next, you will test all the functionality implemented for the application.

      Step 5 — Running Your Application

      Now that you have set up the frontend of this application with React and TypeScript by creating several reusable components, and also built a REST API with the json-server, you can run your app.

      Navigate back to the project’s root folder:

      • cd ~/typescript-react-app

      Next run the following command to start your app:

      Note: Make sure your server is still running in the other terminal window. Otherwise, start it with: yarn server.

      Navigate to http://localhost:3000 to view the application from your browser. Then proceed to click on the Create button and fill in the details of a customer.

      Create customer page

      After entering the appropriate values in the input fields, click on the Create Customer button to submit the form. The application will redirect you back to your homepage once you're done creating a new customer.

      View customers page

      Click the Edit Customer button for any of the rows and you will be directed to the page that hosts the editing functionality for the corresponding customer on that row.

      Edit customer page

      Edit the details of the customer and then click on Edit Customer to update the customer’s details.

      You've run your application to ensure all the components are working. Using the different pages of your application, you've created and edited a customer entry.

      Conclusion

      In this tutorial you built a customer list management app with React and TypeScript. The process in this tutorial is a deviation from using JavaScript as the conventional way of structuring and building applications with React. You've leveraged the benefits of using TypeScript to complete this front-end focused tutorial.

      To continue to develop this project, you can move your mock back-end server to a production-ready back-end technology like Express or Nest.js. Furthermore, you can extend what you have built in this tutorial by adding more features such as authentication and authorization with different tools like the Passport.js authentication library.

      You can find the complete source code for the project on GitHub.



      Source link

      Webinar Series: Kubernetes Package Management with Helm and CI/CD with Jenkins X


      Webinar Series

      This article supplements a webinar series on doing CI/CD with Kubernetes. The series discusses how to take a cloud native approach to building, testing, and deploying applications, covering release management, cloud native tools, service meshes, and CI/CD tools that can be used with Kubernetes. It is designed to help developers and businesses that are interested in integrating CI/CD best practices with Kubernetes into their workflows.

      This tutorial includes the concepts and commands from the second session of the series, Kubernetes Package Management with Helm and CI/CD with Jenkins X.

      Warning: The procedures in this tutorial are meant for demonstration purposes only. As a result, they don’t follow the best practices and security measures necessary for a production-ready deployment.

      Introduction

      In order to reduce error and organize complexity when deploying an application, CI/CD systems must include robust tooling for package management/deployment and pipelines with automated testing. But in modern production environments, the increased complexity of cloud-based infrastructure can present problems for putting together a reliable CI/CD environment. Two Kubernetes-specific tools developed to solve this problem are the Helm package manager and the Jenkins X pipeline automation tool.

      Helm is a package manager specifically designed for Kubernetes, maintained by the Cloud Native Computing Foundation (CNCF) in collaboration with Microsoft, Google, Bitnami, and the Helm contributor community. At a high level, it accomplishes the same goals as Linux system package managers like APT or YUM: managing the installation of applications and dependencies behind the scenes and hiding the complexity from the user. But with Kubernetes, the need for this kind of management is even more pronounced: Installing applications requires the complex and tedious orchestration of YAML files, and upgrading or rolling back releases can be anywhere from difficult to impossible. In order to solve this problem, Helm runs on top of Kubernetes and packages applications into pre-configured resources called charts, which the user can manage with simple commands, making the process of sharing and managing applications more user-friendly.

      Jenkins X is a CI/CD tool used to automate production pipelines and environments for Kubernetes. Using Docker images, Helm charts, and the Jenkins pipeline engine, Jenkins X can automatically manage releases and versions and promote applications between environments on GitHub.

      In this second article of the CI/CD with Kubernetes series, you will preview these two tools by:

      • Managing, creating, and deploying Kubernetes packages with Helm.

      • Building a CI/CD pipeline with Jenkins X.

      Though a variety of Kubernetes platforms can use Helm and Jenkins X, in this tutorial you will run a simulated Kubernetes cluster, set up in your local environment. To do this, you will use Minikube, a program that allows you to try out Kubernetes tools on your own machine without having to set up a true Kubernetes cluster.

      By the end of this tutorial, you will have a basic understanding of how these Kubernetes-native tools can help you implement a CI/CD system for your cloud application.

      Prerequisites

      To follow this tutorial, you will need:

      • An Ubuntu 16.04 server with 16 GB of RAM or above. Since this tutorial is meant for demonstration purposes only, commands are run from the root account. Note that the unrestrained privileges of this account do not adhere to production-ready best practices and could affect your system. For this reason, it is suggested to follow these steps in a test environment such as a virtual machine or a DigitalOcean Droplet.

      • A GitHub account and GitHub API token. Be sure to record this API token so that you can enter it during the Jenkins X portion of this tutorial.

      • Familiarity with Kubernetes concepts. Please refer to the article An Introduction to Kubernetes for more details.

      Step 1 — Creating a Local Kubernetes Cluster with Minikube

      Before setting up Minikube, you will have to install its dependencies, including the Kubernetes command line tool kubectl, the bidirectional data transfer relay socat, and the container program Docker.

      First, make sure that your system’s package manager can access packages over HTTPS with apt-transport-https:

      • apt-get update
      • apt-get install apt-transport-https

      Next, in order to ensure the kubectl download is valid, add the GPG key for the official Google repository to your system:

      • curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -

      Once you have added the GPG key, create the file /etc/apt/sources.list.d/kubernetes.list by opening it in your text editor:

      • nano /etc/apt/sources.list.d/kubernetes.list

      Once this file is open, add the following line:

      /etc/apt/sources.list.d/kubernetes.list

      deb http://apt.kubernetes.io/ kubernetes-xenial main
      

      This will show your system the source for downloading kubectl. Once you have added the line, save and exit the file. With the nano text editor, you can do this by pressing CTRL+X, typing y, and pressing ENTER.

      Finally, update the source list for APT and install kubectl, socat, and docker.io:

      • apt-get update
      • apt-get install -y kubectl socat docker.io

      Note: For Minikube to simulate a Kubernetes cluster, you must download the docker.io package rather than the newer docker-ce release. For production-ready environments, docker-ce would be the more appropriate choice, since it is better maintained in the official Docker repository.

      Now that you have installed kubectl, you can proceed with installing Minikube. First, use curl to download the program’s binary:

      • curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.28.0/minikube-linux-amd64

      Next, change the access permissions of the file you just downloaded so that your system can execute it:

      Finally, copy the minikube file to the executable path at /usr/local/bin/ and remove the original file from your home directory:

      • cp minikube /usr/local/bin/
      • rm minikube

      With Minikube installed on your machine, you can now start the program. To create a Minikube Kubernetes cluster, use the following command:

      • minikube start --vm-driver none

      The flag --vm-driver none instructs Minikube to run Kubernetes on the local host using containers rather than a virtual machine. Running Minikube this way means that you do not need to download a VM driver, but also means that the Kubernetes API server will run insecurely as root.

      Warning: Because the API server with root privileges will have unlimited access to the local host, it is not recommended to run Minikube using the none driver on personal workstations.

      Now that you have started Minikube, check to make sure that your cluster is running with the following command:

      You will receive the following output, with your IP address in place of your_IP_address:

      minikube: Running
      cluster: Running
      kubectl: Correctly Configured: pointing to minikube-vm at your_IP_address
      

      Now that you have set up your simulated Kubernetes cluster using Minikube, you can gain experience with Kubernetes package management by installing and configuring the Helm package manager on top of your cluster.

      Step 2 — Setting Up the Helm Package Manager on your Cluster

      In order to coordinate the installation of applications on your Kubernetes cluster, you will now install the Helm package manager. Helm consists of a helm client that runs outside the cluster and a tiller server that manages application releases from within the cluster. You will have to install and configure both to successfully run Helm on your cluster.

      To install the Helm binaries, first use curl to download the following installation script from the official Helm GitHub repository into a new file named get_helm.sh:

      • curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh

      Since this script requires root access, change the permission of get_helm.sh so that the owner of the file (in this case, root) can read, write, and execute it:

      Now, execute the script:

      When the script finishes, you will have helm installed to /usr/local/bin/helm and tiller installed to /usr/local/bin/tiller.

      Though tiller is now installed, it does not yet have the correct roles and permissions to access the necessary resources in your Kubernetes cluster. To assign these roles and permissions to tiller, you will have to create a service account named tiller. In Kubernetes, a service account represents an identity for processes that run in a pod. After a process is authenticated through a service account, it can then contact the API server and access cluster resources. If a pod is not assigned a specific service account, it gets the default service account. You will also have to create a Role-Based access control (RBAC) rule that authorizes the tiller service account.

      In Kubernetes RBAC API, a role contains rules that determine a set of permissions. A role can be defined with a scope of namespace or cluster, and can only grant access to resources within a single namespace. ClusterRole can create the same permissions on the level of a cluster, granting access to cluster-scoped resources like nodes and namespaced resources like pods. To assign the tiller service account the right role, create a YAML file called rbac_helm.yaml and open it in your text editor:

      Add the following lines to the file to configure the tiller service account:

      rbac_helm.yaml

      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: tiller
        namespace: kube-system
      ---
      apiVersion: rbac.authorization.k8s.io/v1beta1
      kind: ClusterRoleBinding
      metadata:
        name: tiller
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: cluster-admin
      subjects:
        - kind: ServiceAccount
          name: tiller
          namespace: kube-system
      
        - kind: User
          name: "admin"
          apiGroup: rbac.authorization.k8s.io
      
        - kind: User
          name: "kubelet"
          apiGroup: rbac.authorization.k8s.io
      
        - kind: Group
          name: system:serviceaccounts
          apiGroup: rbac.authorization.k8s.io
      

      In the preceding file, ServiceAccount allows the tiller processes to access the apiserver as an authenticated service account. ClusterRole grants certain permissions to a role, and ClusterRoleBinding assigns that role to a list of subjects, including the tiller service account, the admin and kubelet users, and the system:serviceaccounts group.

      Next, deploy the configuration in rbac_helm.yaml with the following command:

      • kubectl apply -f rbac_helm.yaml

      With the tiller configuration deployed, you can now initialize Helm with the --service-acount flag to use the service account you just set up:

      • helm init --service-account tiller

      You will receive the following output, representing a successful initialization:

      Output

      Creating /root/.helm Creating /root/.helm/repository Creating /root/.helm/repository/cache Creating /root/.helm/repository/local Creating /root/.helm/plugins Creating /root/.helm/starters Creating /root/.helm/cache/archive Creating /root/.helm/repository/repositories.yaml Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com Adding local repo with URL: http://127.0.0.1:8879/charts $HELM_HOME has been configured at /root/.helm. Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster. Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy. To prevent this, run `helm init` with the --tiller-tls-verify flag. For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation Happy Helming!

      This creates a tiller pod in the kube-system namespace. It also creates the .helm default repository in your $HOME directory and configures the default Helm stable chart repository at https://kubernetes-charts.storage.googleapis.com and the local Helm repository at http://127.0.0.1:8879/charts.

      To make sure that the tiller pod is running in the kube-system namespace, enter the following command:

      • kubectl --namespace kube-system get pods

      In your list of pods, tiller-deploy will appear, as is shown in the following output:

      Output

      NAME READY STATUS RESTARTS AGE etcd-minikube 1/1 Running 0 2h kube-addon-manager-minikube 1/1 Running 0 2h kube-apiserver-minikube 1/1 Running 0 2h kube-controller-manager-minikube 1/1 Running 0 2h kube-dns-86f4d74b45-rjql8 3/3 Running 0 2h kube-proxy-dv268 1/1 Running 0 2h kube-scheduler-minikube 1/1 Running 0 2h kubernetes-dashboard-5498ccf677-wktkl 1/1 Running 0 2h storage-provisioner 1/1 Running 0 2h tiller-deploy-689d79895f-bggbk 1/1 Running 0 5m

      If the tiller pod's status is Running, it can now manage Kubernetes applications from inside your cluster on behalf of Helm.

      To make sure that the entire Helm application is working, search the Helm package repositiories for an application like MongoDB:

      In the output, you will see a list of possible applications that fit your search term:

      Output

      NAME CHART VERSION APP VERSION DESCRIPTION stable/mongodb 5.4.0 4.0.6 NoSQL document-oriented database that stores JSON-like do... stable/mongodb-replicaset 3.9.0 3.6 NoSQL document-oriented database that stores JSON-like do... stable/prometheus-mongodb-exporter 1.0.0 v0.6.1 A Prometheus exporter for MongoDB metrics stable/unifi 0.3.1 5.9.29 Ubiquiti Network's Unifi Controller

      Now that you have installed Helm on your Kubernetes cluster, you can learn more about the package manager by creating a sample Helm chart and deploying an application from it.

      Step 3 — Creating a Chart and Deploying an Application with Helm

      In the Helm package manager, individual packages are called charts. Within a chart, a set of files defines an application, which can vary in complexity from a pod to a structured, full-stack app. You can download charts from the Helm repositories, or you can use the helm create command to create your own.

      To test out the capabilities of Helm, create a new Helm chart named demo with the following command:

      In your home directory, you will find a new directory called demo, within which you can create and edit your own chart templates.

      Move into the demo directory and use ls to list its contents:

      You will find the following files and directories in demo:

      demo

      charts  Chart.yaml  templates  values.yaml
      

      Using your text editor, open up the Chart.yaml file:

      Inside, you will find the following contents:

      demo/Chart.yaml

      apiVersion: v1
      appVersion: "1.0"
      description: A Helm chart for Kubernetes
      name: demo
      version: 0.1.0
      

      In this Chart.yaml file, you will find fields like apiVersion, which must be always v1, a description that gives additional information about what demo is, the name of the chart, and the version number, which Helm uses as a release marker. When you are done examining the file, close out of your text editor.

      Next, open up the values.yaml file:

      In this file, you will find the following contents:

      demo/values.yaml

      # Default values for demo.
      # This is a YAML-formatted file.
      # Declare variables to be passed into your templates.
      
      replicaCount: 1
      
      image:
        repository: nginx
        tag: stable
        pullPolicy: IfNotPresent
      
      nameOverride: ""
      fullnameOverride: ""
      
      service:
        type: ClusterIP
        port: 80
      
      ingress:
        enabled: false
        annotations: {}
          # kubernetes.io/ingress.class: nginx
          # kubernetes.io/tls-acme: "true"
        paths: []
        hosts:
          - chart-example.local
        tls: []
        #  - secretName: chart-example-tls
        #    hosts:
        #      - chart-example.local
      
      resources: {}
        # We usually recommend not to specify default resources and to leave this as a conscious
        # choice for the user. This also increases chances charts run on environments with little
        # resources, such as Minikube. If you do want to specify resources, uncomment the following
        # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
        # limits:
        #  cpu: 100m
        #  memory: 128Mi
        # requests:
        #  cpu: 100m
        #  memory: 128Mi
      
      nodeSelector: {}
      
      tolerations: []
      
      affinity: {}
      

      By changing the contents of values.yaml, chart developers can supply default values for the application defined in the chart, controlling replica count, image base, ingress access, secret management, and more. Chart users can supply their own values for these parameters with a custom YAML file using helm install. When a user provides custom values, these values will override the values in the chart’s values.yaml file.

      Close out the values.yaml file and list the contents of the templates directory with the following command:

      Here you will find templates for various files that can control different aspects of your chart:

      templates

      deployment.yaml  _helpers.tpl  ingress.yaml  NOTES.txt  service.yaml  tests
      

      Now that you have explored the demo chart, you can experiment with Helm chart installation by installing demo. Return to your home directory with the following command:

      Install the demo Helm chart under the name web with helm install:

      • helm install --name web ./demo

      You will get the following output:

      Output

      NAME: web LAST DEPLOYED: Wed Feb 20 20:59:48 2019 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1/Service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE web-demo ClusterIP 10.100.76.231 <none> 80/TCP 0s ==> v1/Deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE web-demo 1 0 0 0 0s ==> v1/Pod(related) NAME READY STATUS RESTARTS AGE web-demo-5758d98fdd-x4mjs 0/1 ContainerCreating 0 0s NOTES: 1. Get the application URL by running these commands: export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=demo,app.kubernetes.io/instance=web" -o jsonpath="{.items[0].metadata.name}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl port-forward $POD_NAME 8080:80

      In this output, you will find the STATUS of your application, plus a list of relevant resources in your cluster.

      Next, list the deployments created by the demo Helm chart with the following command:

      This will yield output that will list your active deployments:

      Output

      NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE web-demo 1 1 1 1 4m

      Listing your pods with the command kubectl get pods would show the pods that are running your web application, which would look like the following:

      Output

      NAME READY STATUS RESTARTS AGE web-demo-5758d98fdd-nbkqd 1/1 Running 0 4m

      To demonstrate how changes in the Helm chart can release different versions of your application, open up demo/values.yaml in your text editor and change replicaCount: to 3 and image:tag: from stable to latest. In the following code block, you will find what the YAML file should look like after you have finished modifying it, with the changes highlighted:

      demo/values.yaml

      # Default values for demo.
      # This is a YAML-formatted file.
      # Declare variables to be passed into your templates.
      
      replicaCount: 3
      
      image:
        repository: nginx
        tag: latest
        pullPolicy: IfNotPresent
      
      nameOverride: ""
      fullnameOverride: ""
      
      service:
        type: ClusterIP
        port: 80
      . . .
      

      Save and exit the file.

      Before you deploy this new version of your web application, list your Helm releases as they are now with the following command:

      You will receive the following output, with the one deployment you created earlier:

      Output

      NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE web 1 Wed Feb 20 20:59:48 2019 DEPLOYED demo-0.1.0 1.0 default

      Notice that REVISION is listed as 1, indicating that this is the first revision of the web application.

      To deploy the web application with the latest changes made to demo/values.yaml, upgrade the application with the following command:

      Now, list the Helm releases again:

      You will receive the following output:

      Output

      NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE web 2 Wed Feb 20 21:18:12 2019 DEPLOYED demo-0.1.0 1.0 default

      Notice that REVISION has changed to 2, indicating that this is the second revision.

      To find the history of the Helm releases for web, use the following:

      This will show both of the revisions of the web application:

      Output

      REVISION        UPDATED                         STATUS          CHART           DESCRIPTION
      1               Wed Feb 20 20:59:48 2019        SUPERSEDED      demo-0.1.0      Install complete
      2               Wed Feb 20 21:18:12 2019        DEPLOYED        demo-0.1.0      Upgrade complete
      

      To roll back your application to revision 1, enter the following command:

      This will yield the following output:

      Output

      Rollback was a success! Happy Helming!

      Now, bring up the Helm release history:

      You will receive the following list:

      Output

      REVISION UPDATED STATUS CHART DESCRIPTION 1 Wed Feb 20 20:59:48 2019 SUPERSEDED demo-0.1.0 Install complete 2 Wed Feb 20 21:18:12 2019 SUPERSEDED demo-0.1.0 Upgrade complete 3 Wed Feb 20 21:28:48 2019 DEPLOYED demo-0.1.0 Rollback to 1

      By rolling back the web application, you have created a third revision that has the same settings as revision 1. Remember, you can always tell which revision is active by finding the DEPLOYED item under STATUS.

      To prepare for the next section, clean up your testing area by deleting your web release with the helm delete command:

      Examine the Helm release history again:

      You will receive the following output:

      Output

      REVISION UPDATED STATUS CHART DESCRIPTION 1 Wed Feb 20 20:59:48 2019 SUPERSEDED demo-0.1.0 Install complete 2 Wed Feb 20 21:18:12 2019 SUPERSEDED demo-0.1.0 Upgrade complete 3 Wed Feb 20 21:28:48 2019 DELETED demo-0.1.0 Deletion complete

      The STATUS for REVISION 3 has changed to DELETED, indicating that your deployed instance of web has been deleted. However, although this does delete the release, it does not delete it from store. In order to delete the release completely, run the helm delete command with the --purge flag.

      In this step, you have managed application releases on Kubernetes with the Helm. If you would like to study Helm further, check out our An Introduction to Helm, the Package Manager for Kubernetes tutorial, or review the official Helm documentation.

      Next, you will set up and test the pipeline automation tool Jenkins X by using the jx CLI to create a CI/CD-ready Kubernetes cluster.

      Step 4 — Setting Up the Jenkins X Environment

      With Jenkins X, you can create your Kubernetes cluster from the ground up with pipeline automation and CI/CD solutions built in. By installing the jx CLI tool, you will be able to efficiently manage application releases, Docker images, and Helm charts, in addition to automatically promoting your applications across environments in GitHub.

      Since you will be using jx to create your cluster, you must first delete the Minikube cluster that you already have. To do this, use the following command:

      This will delete the local simulated Kubernete cluster, but will not delete the default directories created when you first installed Minikube. To clean these off your machine, use the following commands:

      • rm -rf ~/.kube
      • rm -rf ~/.minikube
      • rm -rf /etc/kubernetes/*
      • rm -rf /var/lib/minikube/*

      Once you have completely cleared Minikube from your machine, you can move on to installing the Jenkins X binary.

      First, download the compressed jx file from the official Jenkins X GitHub repository with the curl command and uncompress it with the tar command:

      • curl -L https://github.com/jenkins-x/jx/releases/download/v1.3.781/jx-linux-amd64.tar.gz | tar xzv

      Next, move the downloaded jx file to the executable path at /usr/local/bin:

      Jenkins X comes with a Docker Registry that runs inside your Kubernetes cluster. Since this is an internal element, security measures such as self-signed certificates can cause trouble for the program. To fix this, set Docker to use insecure registries for the local IP range. To do this, create the file /etc/docker/daemon.json and open it in your text editor:

      • nano /etc/docker/daemon.json

      Add the following contents to the file:

      /etc/docker/daemon.json

      {
        "insecure-registries" : ["0.0.0.0/0"]
      }
      

      Save and exit the file. For these changes to take effect, restart the Docker service with the following command:

      To verify that you have configured Docker with insecure registries, use the following command:

      At the end of the output, you should see the following highlighted line:

      Output

      Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 15 Server Version: 18.06.1-ce Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Native Overlay Diff: true . . . Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 0.0.0.0/0 127.0.0.0/8 Live Restore Enabled: false

      Now that you have downloaded Jenkins X and configured the Docker registry, use the jx CLI tool to create a Minikube Kubernetes cluster with CI/CD capabilities:

      • jx create cluster minikube --cpu=5 --default-admin-password=admin --vm-driver=none --memory=13314

      Here you are creating a Kubernetes cluster using Minikube, with the flag --cpu=5 to set 5 CPUs and --memory=13314 to give your cluster 13314 MBs of memory. Since Jenkins X is a robust but large program, these specifications will ensure that Jenkins X works without problems in this demonstration. Also, you are using --default-admin-password=admin to set the Jenkins X password as admin and --vm-driver=none to set up the cluster locally, as you did in Step 1.

      As Jenkins X spins up your cluster, you will receive various prompts at different times throughout the process that set the parameters for your cluster and determine how it will communicate with GitHub to manage your production environments.

      First, you will receive the following prompt:

      Output

      ? disk-size (MB) 150GB

      Press ENTER to continue. Next, you will be prompted for the name you wish to use with git, the email address you wish to use with git, and your GitHub username. Enter each of these when prompted, then press ENTER.

      Next, Jenkins X will prompt you to enter your GitHub API token:

      Output

      To be able to create a repository on GitHub we need an API Token Please click this URL https://github.com/settings/tokens/new?scopes=repo,read:user,read:org,user:email,write:repo_hook,delete_repo Then COPY the token and enter in into the form below: ? API Token:

      Enter your token here, or create a new token with the appropriate permissions using the highlighted URL in the preceding code block.

      Next, Jenkins X will ask:

      Output

      ? Do you wish to use GitHub as the pipelines Git server: (Y/n) ? Do you wish to use your_GitHub_username as the pipelines Git user for GitHub server: (Y/n)

      Enter Y for both questions.

      After this, Jenkins X will prompt you to answer the following:

      Output

      ? Select Jenkins installation type: [Use arrows to move, type to filter] >Static Master Jenkins Serverless Jenkins ? Pick workload build pack: [Use arrows to move, type to filter] > Kubernetes Workloads: Automated CI+CD with GitOps Promotion Library Workloads: CI+Release but no CD

      For the prior, select Static Master Jenkins, and select Kubernetes Workloads: Automated CI+CD with GitOps Promotion for the latter. When prompted to select an organization for your environment repository, select your GitHub username.

      Finally, you will receive the following output, which verifies successful installation and provides your Jenkins X admin password.

      Output

      Creating GitHub webhook for your_GitHub_username/environment-horsehelix-production for url http://jenkins.jx.your_IP_address.nip.io/github-webhook/ Jenkins X installation completed successfully ******************************************************** NOTE: Your admin password is: admin ******************************************************** Your Kubernetes context is now set to the namespace: jx To switch back to your original namespace use: jx namespace default For help on switching contexts see: https://jenkins-x.io/developing/kube-context/ To import existing projects into Jenkins: jx import To create a new Spring Boot microservice: jx create spring -d web -d actuator To create a new microservice from a quickstart: jx create quickstart

      Next, use the jx get command to receive a list of URLs that show information about your application:

      This command will yield a list similar to the following:

      Name                      URL
      jenkins                   http://jenkins.jx.your_IP_address.nip.io
      jenkins-x-chartmuseum     http://chartmuseum.jx.your_IP_address.nip.io
      jenkins-x-docker-registry http://docker-registry.jx.your_IP_address.nip.io
      jenkins-x-monocular-api   http://monocular.jx.your_IP_address.nip.io
      jenkins-x-monocular-ui    http://monocular.jx.your_IP_address.nip.io
      nexus                     http://nexus.jx.your_IP_address.nip.io
      

      You can use the URLs to view Jenkins X data about your CI/CD environment via a UI by entering the address into your browser and entering your username and password. In this case, this will be "admin" for both.

      Next, in order to ensure that the service accounts in the namespaces jx, jx-staging, and jx-production have admin privileges, modify your RBAC policies with the following commands:

      • kubectl create clusterrolebinding jx-staging1 --clusterrole=cluster-admin --user=admin --user=expose --group=system:serviceaccounts --serviceaccount=jx-staging:expose --namespace=jx-staging
      • kubectl create clusterrolebinding jx-staging2 --clusterrole=cluster-admin --user=admin --user=expose --group=system:serviceaccounts --serviceaccount=jx-staging:default --namespace=jx-staging
      • kubectl create clusterrolebinding jx-production1 --clusterrole=cluster-admin --user=admin --user=expose --group=system:serviceaccounts --serviceaccount=jx-production:expose --namespace=jx-productions
      • kubectl create clusterrolebinding jx-production2 --clusterrole=cluster-admin --user=admin --user=expose --group=system:serviceaccounts --serviceaccount=jx-production:default --namespace=jx-productions
      • kubectl create clusterrolebinding jx-binding1 --clusterrole=cluster-admin --user=admin --user=expose --group=system:serviceaccounts --serviceaccount=jx:expose --namespace=jx
      • kubectl create clusterrolebinding jx-binding2 --clusterrole=cluster-admin --user=admin --user=expose --group=system:serviceaccounts --serviceaccount=jx:default --namespace=jx

      Now that you have created your local Kubernetes cluster with Jenkins X functionality built in, you can move on to creating an application on the platform to test its CI/CD capabilities and experience a Jenkins X pipeline.

      Step 5 — Creating a Test Application in Your Jenkins X Environment

      With your Jenkins X environment set up in your Kubernetes cluster, you now have CI/CD infrastructure in place that can help you automate a testing pipeline. In this step, you will try this out by setting up a test application in a working Jenkins X pipeline.

      For demonstration purposes, this tutorial will use a sample RSVP application created by the CloudYuga team. You can find this application, along with other webinar materials, at the DO-Community GitHub repository.

      First, clone the sample application from the repository with the following command:

      • git clone https://github.com/do-community/rsvpapp.git

      Once you've cloned the repository, move into the rsvpapp directory and remove the git files:

      To initialize a git repository and a Jenkins X project for a new application, you can use jx create to start from scratch or a template, or jx import to import an existing application from a local project or git repository. For this tutorial, import the sample RSVP application by running the following command from within the application's home directory:

      Jenkins X will prompt you for your GitHub username, whether you'd like to initialize git, a commit message, your organization, and the name you would like for your repository. Answer yes to initialize git, then provide the rest of the prompts with your individual GitHub information and preferences. As Jenkins X imports the application, it will create Helm charts and a Jenkinsfile in your application's home directory. You can modify these charts and the Jenkinsfile as per your requirements.

      Since the sample RSVP application runs on port 5000 of its container, modify your charts/rsvpapp/values.yaml file to match this. Open the charts/rsvpapp/values.yaml in your text editor:

      • nano charts/rsvpapp/values.yaml

      In this values.yaml file, set service:internalPort: to 5000. Once you have made this change, your file should look like the following:

      charts/rsvpapp/values.yaml

      # Default values for python.
      # This is a YAML-formatted file.
      # Declare variables to be passed into your templates.
      replicaCount: 1
      image:
        repository: draft
        tag: dev
        pullPolicy: IfNotPresent
      service:
        name: rsvpapp
        type: ClusterIP
        externalPort: 80
        internalPort: 5000
        annotations:
          fabric8.io/expose: "true"
          fabric8.io/ingress.annotations: "kubernetes.io/ingress.class: nginx"
      resources:
        limits:
          cpu: 100m
          memory: 128Mi
        requests:
          cpu: 100m
          memory: 128Mi
      ingress:
        enabled: false
      

      Save and exit your file.

      Next, change the charts/preview/requirements.yaml to fit with your application. requirements.yaml is a YAML file in which developers can declare chart dependencies, along with the location of the chart and the desired version. Since our sample application uses MongoDB for database purposes, you'll need to modify the charts/preview/requirements.yaml file to list MongoDB as a dependency. Open the file in your text editor with the following command:

      • nano charts/preview/requirements.yaml

      Edit the file by adding the mongodb-replicaset entry after the alias: cleanup entry, as is highlighted in the following code block:

      charts/preview/requirements.yaml

      # !! File must end with empty line !!
      dependencies:
      - alias: expose
        name: exposecontroller
        repository: http://chartmuseum.jenkins-x.io
        version: 2.3.92
      - alias: cleanup
        name: exposecontroller
        repository: http://chartmuseum.jenkins-x.io
        version: 2.3.92
      - name: mongodb-replicaset
        repository: https://kubernetes-charts.storage.googleapis.com/
        version: 3.5.5
      
        # !! "alias: preview" must be last entry in dependencies array !!
        # !! Place custom dependencies above !!
      - alias: preview
        name: rsvpapp
        repository: file://../rsvpapp
      

      Here you have specified the mongodb-replicaset chart as a dependency for the preview chart.

      Next, repeat this process for your rsvpapp chart. Create the charts/rsvpapp/requirements.yaml file and open it in your text editor:

      • nano charts/rsvpapp/requirements.yaml

      Once the file is open, add the following, making sure that there is a single line of empty space before and after the populated lines:

      charts/rsvpapp/requirements.yaml

      
      dependencies:
      - name: mongodb-replicaset
        repository: https://kubernetes-charts.storage.googleapis.com/
        version: 3.5.5
      
      

      Now you have specified the mongodb-replicaset chart as a dependency for your rsvpapp chart.

      Next, in order to connect the frontend of the sample RSVP application to the MongoDB backend, add a MONGODB_HOST environment variable to your deployment.yaml file in charts/rsvpapp/templates/. Open this file in your text editor:

      • nano charts/rsvpapp/templates/deployment.yaml

      Add the following highlighted lines to the file, in addition to one blank line at the top of the file and two blank lines at the bottom of the file. Note that these blank lines are required for the YAML file to work:

      charts/rsvpapp/templates/deployment.yaml

      
      apiVersion: extensions/v1beta1
      kind: Deployment
      metadata:
        name: {{ template "fullname" . }}
        labels:
          draft: {{ default "draft-app" .Values.draft }}
          chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
      spec:
        replicas: {{ .Values.replicaCount }}
        template:
          metadata:
            labels:
              draft: {{ default "draft-app" .Values.draft }}
              app: {{ template "fullname" . }}
      {{- if .Values.podAnnotations }}
            annotations:
      {{ toYaml .Values.podAnnotations | indent 8 }}
      {{- end }}
          spec:
            containers:
            - name: {{ .Chart.Name }}
              image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
              env:
              - name: MONGODB_HOST
                value: "mongodb://{{.Release.Name}}-mongodb-replicaset-0.{{.Release.Name}}-mongodb-replicaset,{{.Release.Name}}-mongodb-replicaset-1.{{.Release.Name}}-mongodb-replicaset,{{.Release.Name}}-mongodb-replicaset-2.{{.Release.Name}}-mongodb-replicaset:27017"
              imagePullPolicy: {{ .Values.image.pullPolicy }}
              ports:
              - containerPort: {{ .Values.service.internalPort }}
              resources:
      {{ toYaml .Values.resources | indent 12 }}
      
      
      

      With these changes, Helm will be able to deploy your application with MongoDB as its database.

      Next, examine the Jenkinsfile generated by Jenkins X by opening the file from your application's home directory:

      This Jenkinsfile defines the pipeline that is triggered every time you commit a version of your application to your GitHub repository. If you wanted to automate your code testing so that the tests are triggered every time the pipeline is triggered, you would add the test to this document.

      To demonstrate this, add a customized test case by replacing sh "python -m unittest" under stage('CI Build and push snapshot') and stage('Build Release') in the Jenkinsfile with the following highlighted lines:

      /rsvpapp/Jenkinsfile

      . . .
        stages {
          stage('CI Build and push snapshot') {
            when {
              branch 'PR-*'
            }
            environment {
              PREVIEW_VERSION = "0.0.0-SNAPSHOT-$BRANCH_NAME-$BUILD_NUMBER"
              PREVIEW_NAMESPACE = "$APP_NAME-$BRANCH_NAME".toLowerCase()
              HELM_RELEASE = "$PREVIEW_NAMESPACE".toLowerCase()
            }
            steps {
              container('python') {
                sh "pip install -r requirements.txt"
                sh "python -m pytest tests/test_rsvpapp.py"
                sh "export VERSION=$PREVIEW_VERSION && skaffold build -f skaffold.yaml"
                sh "jx step post build --image $DOCKER_REGISTRY/$ORG/$APP_NAME:$PREVIEW_VERSION"
                dir('./charts/preview') {
                  sh "make preview"
                  sh "jx preview --app $APP_NAME --dir ../.."
                }
              }
            }
          }
          stage('Build Release') {
            when {
              branch 'master'
            }
            steps {
              container('python') {
      
                // ensure we're not on a detached head
                sh "git checkout master"
                sh "git config --global credential.helper store"
                sh "jx step git credentials"
      
                // so we can retrieve the version in later steps
                sh "echo $(jx-release-version) > VERSION"
                sh "jx step tag --version $(cat VERSION)"
                sh "pip install -r requirements.txt"
                sh "python -m pytest tests/test_rsvpapp.py"
                sh "export VERSION=`cat VERSION` && skaffold build -f skaffold.yaml"
                sh "jx step post build --image $DOCKER_REGISTRY/$ORG/$APP_NAME:$(cat VERSION)"
              }
            }
          }
      . . .
      

      With the added lines, the Jenkins X pipeline will install dependencies and carry out a Python test whenever you commit a change to your application.

      Now that you have changed the sample RSVP application, commit and push these changes to GitHub with the following commands:

      • git add *
      • git commit -m update
      • git push

      When you push these changes to GitHub, you will trigger a new build of your application. If you open the Jenkins UI by navigating to http://jenkins.jx.your_IP_address.nip.io and entering "admin" for your username and password, you will find information about your new build. If you click "Build History" from the menu on the left side of the page, you should see a history of your committed builds. If you click on the blue icon next to a build then select "Console Ouput" from the lefthand menu, you will find the console output for the automated steps in your pipeline. Scrolling to the end of this output, you will find the following message:

      Output

      . . . Finished: SUCCESS

      This means that your application has passed your customized tests and is now successfully deployed.

      Once Jenkins X builds the application release, it will promote the application to the staging environment. To verify that your application is running, list the applications running on your Kubernetes cluster by using the following command:

      You will receive output similar to the following:

      Output

      APPLICATION STAGING PODS URL rsvpapp 0.0.2 1/1 http://rsvpapp.jx-staging.your_IP_address.nip.io

      From this, you can see that Jenkins X has deployed your application in your jx-staging environment as version 0.0.2. The output also shows the URL that you can use to access your application. Visiting this URL will show you the sample RSVP application:

      Sample RSVP Application in the Staging Environment

      Next, check out the activity of your application with the following command:

      • jx get activity -f rsvpapp

      You will receive output similar to the following:

      Output

      STEP STARTED AGO DURATION STATUS your_GitHub_username/rsvpappv/master #1 3h42m23s 4m51s Succeeded Version: 0.0.1 Checkout Source 3h41m52s 6s Succeeded CI Build and push snapshot 3h41m46s NotExecuted Build Release 3h41m46s 56s Succeeded Promote to Environments 3h40m50s 3m17s Succeeded Promote: staging 3h40m29s 2m36s Succeeded PullRequest 3h40m29s 1m16s Succeeded PullRequest: https://github.com/your_GitHub_username/environment-horsehelix-staging/pull/1 Merge SHA: dc33d3747abdacd2524e8c22f0b5fbb2ac3f6fc7 Update 3h39m13s 1m20s Succeeded Status: Success at: http://jenkins.jx.your_IP_address.nip.io/job/your_GitHub_username/job/environment-horsehelix-staging/job/master/2/display/redirect Promoted 3h39m13s 1m20s Succeeded Application is at: http://rsvpapp.jx-staging.your_IP_address.nip.io Clean up 3h37m33s 1s Succeeded your_GitHub_username/rsvpappv/master #2 28m37s 5m57s Succeeded Version: 0.0.2 Checkout Source 28m18s 4s Succeeded CI Build and push snapshot 28m14s NotExecuted Build Release 28m14s 56s Succeeded Promote to Environments 27m18s 4m38s Succeeded Promote: staging 26m53s 4m0s Succeeded PullRequest 26m53s 1m4s Succeeded PullRequest: https://github.com/your_GitHub_username/environment-horsehelix-staging/pull/2 Merge SHA: 976bd5ad4172cf9fd79f0c6515f5006553ac6611 Update 25m49s 2m56s Succeeded Status: Success at: http://jenkins.jx.your_IP_address.nip.io/job/your_GitHub_username/job/environment-horsehelix-staging/job/master/3/display/redirect Promoted 25m49s 2m56s Succeeded Application is at: http://rsvpapp.jx-staging.your_IP_address.nip.io Clean up 22m40s 0s Succeeded

      Here you are getting the Jenkins X activity for the RSVP application by applying a filter with -f rsvpapp.

      Next, list the pods running in the jx-staging namespace with the following command:

      • kubectl get pod -n jx-staging

      You will receive output similar to the following:

      NAME                                 READY     STATUS    RESTARTS   AGE
      jx-staging-mongodb-replicaset-0      1/1       Running   0          6m
      jx-staging-mongodb-replicaset-1      1/1       Running   0          6m
      jx-staging-mongodb-replicaset-2      1/1       Running   0          5m
      jx-staging-rsvpapp-c864c4844-4fw5z   1/1       Running   0          6m
      

      This output shows that your application is running in the jx-staging namespace, along with three pods of the backend MongoDB database, adhering to the changes you made to the YAML files earlier.

      Now that you have run a test application through the Jenkins X pipeline, you can try out promoting this application to the production environment.

      To finish up this demonstration, you will complete the CI/CD process by promoting the sample RSVP application to your jx-production namespace.

      First, use jx promote in the following command:

      • jx promote rsvpapp --version=0.0.2 --env=production

      This will promote the rsvpapp application running with version=0.0.2 to the production environment. Throughout the build process, Jenkins X will prompt you to enter your GitHub account information. Answer these prompts with your individual responses as they appear.

      After successful promotion, check the list of applications:

      You will receive output similar to the following:

      Output

      APPLICATION STAGING PODS URL PRODUCTION PODS URL rsvpapp 0.0.2 1/1 http://rsvpapp.jx-staging.your_IP_address.nip.io 0.0.2 1/1 http://rsvpapp.jx-production.your_IP_address.nip.io

      With this PRODUCTION information, you can confirm that Jenkins X has promoted rsvpapp to the production environment. For further verification, visit the production URL http://rsvpapp.jx-production.your_IP_address.nip.io in your browser. You should see the working application, now runnning from "production":

      Sample RSVP Application in the Production Environment

      Finally, list your pods in the jx-production namespace.

      • kubectl get pod -n jx-production

      You will find that rsvpapp and the MongoDB backend pods are running in this namespace:

      NAME                                     READY     STATUS    RESTARTS   AGE
      jx-production-mongodb-replicaset-0       1/1       Running   0          1m
      jx-production-mongodb-replicaset-1       1/1       Running   0          1m
      jx-production-mongodb-replicaset-2       1/1       Running   0          55s
      jx-production-rsvpapp-54748d68bd-zjgv7   1/1       Running   0          1m 
      

      This shows that you have successfully promoted the RSVP sample application to your production environment, simulating the production-ready deployment of an application at the end of a CI/CD pipeline.

      Conclusion

      In this tutorial, you used Helm to manage packages on a simulated Kubernetes cluster and customized a Helm chart to package and deploy your own application. You also set up a Jenkins X environment on your Kubernetes cluster and run a sample application through a CI/CD pipeline from start to finish.

      You now have experience with these tools that you can use when building a CI/CD system on your own Kubernetes cluster. If you'd like to learn more about Helm, check out our An Introduction to Helm, the Package Manager for Kubernetes and How To Install Software on Kubernetes Clusters with the Helm Package Manager articles. To explore further CI/CD tools on Kubernetes, you can read about the Istio service mesh in the next tutorial in this webinar series.



      Source link

      The 8 Best Web Management Tools for Small Businesses in 2019


      From site design to social media and everything in between, managing your web presence is integral in today’s market. There are several reasons why you need a strong online presence: it reinforces your brand, boosts your marketing efforts, and most importantly, helps you connect with your customers.

      But what if you’ve got a local clientele or are a brick-and-mortar operation that’s not really interested in doing the whole e-commerce thing? Do you really need a small-business website?

      The answer is an internet-troll-style, all-caps, gigantic YES: 97 percent of consumers use the web to search for local businesses. If you want your small business to reach its full potential, you need to have an online presence.

      But where do you start? After all, there are thousands of tools out there that claim they help with web management. So we’ve put our fingers on the keyboard and nose to the grindstone — gross, right? — to research the best web management tools for your small business. Here’s what we found.

      Web Design

      When it comes to web design you’ve got several options. Of course, you can always hire a professional team from a trusted firm to tackle your web design, but experts don’t come cheap. If you’re looking for a more cost-effective method, you’ll be pleased to learn that there are several programs that are easy to navigate, have beautiful templates, and provide easy click-to-edit functionality to make creating a beautiful website easy — even if you don’t know how to code or aren’t a professional designer. Here are some of the best.

      1. WordPress

      If you’re at all web savvy, WordPress is an excellent choice. WordPress is a content management system (CMS) that makes it easy to regularly update your website with fresh blog posts, news stories, and more. WordPress does have a slight learning curve, but it’s relatively easy to tackle and you’ll be pleased with the number of features.

      • Flexibility — WordPress is extremely versatile. You can create a blog, a portfolio, a business website, an online community, an online store, and basically anything else you can think of.
      • Simplicity — Not only is WordPress flexible, but it’s also simple to use. With WordPress, you don’t have to know how to code to get a responsive website up in minutes. Plus, tools like DreamPress are specifically built to make maintaining your site and keeping it secure really easy.
      • Popular — WordPress is one of the most popular web design tools on the market. That means there are thousands of pre-designed themes, useful plugins, and resources to help you build, modify, and update your site with ease.
      • Cost — Since WordPress.org software is free, it’s a budget-friendly option that can scale with your business as you grow and need more features.

      Get More with DreamPress

      DreamPress Plus and Pro users get access to Jetpack Professional (and 200+ premium themes) at no added cost!

      2. Remixer

      Remixer is another top-of-the-line website building tool that was whipped up fresh in DreamHost’s developer kitchen. Here’s why it’s great: if you need a DIY website that doesn’t look homemade, Remixer is a click-to-edit solution. So what else does Remixer have going for it? Glad you asked.

      • Responsive — With Remixer, you can rest assured that your website is 100 percent responsive. This means no matter what device your visitors are using to find your site, they’ll have a great experience. In 2018, a mobile-optimized website is a must for small-biz owners.
      • Easy — Remixer is designed with ease in mind. That’s why the click-to-edit interface makes it easy to import your own images. If you don’t have your own art, the royalty-free image library takes care of that too.
      • Quick — Just a few clicks on Remixer and you can get a website up and running in 10 minutes. Yeah, we timed it.
      • Easy to Export  You can start your site in Remixer and then easily export it to WordPress when you’re ready to add e-commerce or blogging functionality.

      Need a Beautiful Website?

      Design it yourself with Remixer, our easy-to-use website builder. No coding required.

      E-Commerce Software

      If you want e-commerce capabilities, we’d recommend building your website with WordPress — the flexibility and scalability are ideal for online stores. But remember: WordPress doesn’t come with an e-commerce solution out of the box. You’ll have to add that functionality, either via a plugin or an additional platform, to create a digital shopping cart.

      3. WooCommerce

      WooCommerce is a popular WordPress plugin and one of the best e-commerce solutions on the market. And it’s easy to get started! Basic features to help you manage payment, shipping, and other common tasks come baked in. If you need additional options, however, it’s easy to find extensions to add functionality.

      • Budget-Friendly — Both WordPress.org software and WooCommerce are completely free, as are some of the extensions (most of the others range from around $50 to $100). Getting your online store up and running doesn’t have to burn a hole in your pocket.
      • Scalable — WooCommerce makes scaling from small business to larger venture easy. For example, if you need more payment gateways, simply download and install a relevant extension or plugin.
      • Secure — Security is vital for every website but especially if you’re managing transactions. WordPress’ dedication to security and frequent updates will help protect your site. Additionally, customers using DreamHost’s managed WordPress options are protected by a built-in firewall that eliminates the need for additional security plugins.
      • Search Engine Optimized — WooCommerce itself is light on SEO-related features. It’s built using code optimized for SEO, and there are a few relevant extensions you can add, such as SEO Friendly Images. However, if you really want to improve your store’s chances of getting to the first page of Google, you can download a WordPress SEO plugin.

      Your Store Deserves WooCommerce Hosting

      Sell anything, anywhere, anytime on the world’s biggest eCommerce platform.

      4. Shopify

      Shopify is a subscription-based, e-commerce platform that makes it easy for beginners to build and customize an online store in very little time. It also provides a “guru” service to guide you through the creation and management of your e-commerce site.

      • Reliable — Shopify is one of the largest e-commerce solutions on the market. Because it requires a monthly subscription — plans currently start at $29 per month — you can count on Shopify’s rock-solid performance.
      • Integratable — It’s easy for DreamHost customers to integrate a Shopify store with your site. There’s also an app store where you can download add-ons that provide functionality such as social media, shipping, and accounting.
        Support — Shopify provides support through its comprehensive help center, where you can find everything from tutorials to troubleshooting guides. You also get access to 24/7 support via chat, email, or phone.

      Still wondering whether you should opt for WooCommerce or Shopify when building your e-commerce site? Wonder no more. We’ve put together a comprehensive guide to help you parse out which platform might be more suitable for your website.

      Social Media

      Even a small business can have a large social media presence. The social tools below will allow you to expand your web presence all in one integrated interface. Be in several places at once? It’s every small business owner’s dream!

      5. HootSuite

      HootSuite integrates with Facebook, Facebook Pages, Twitter, Instagram, Google+, Google+ Pages, LinkedIn, FourSquare, MySpace, YouTube, and WordPress. It allows you to schedule posts ahead of time, focusing on peak performance times for each platform.

      • Convenience — If you’re sick of switching between your social media profiles just to push out daily updates, then you’re going to love HootSuite. With HootSuite, you can manage all your social platforms from one location.
      • Collaboration — Many small businesses have several people creating content and posting across channels. If you don’t have a cohesive system, you’re setting yourself up for a social snafu. HootSuite gets everyone on the same page.
      • Reputation Management — HootSuite makes it possible to never miss a mention or complaint that your team needs to address.

      6. Buffer

      Buffer supports up to 25 social accounts, allowing you to post across all your platforms from one interface. Here are some of Buffer’s other top features.

      • Trends — Buffer has integrated analytics and insights, making it easier to tweak your social strategy based on data.
      • Scheduling — Buffer allows you to create a custom schedule for each day. You can do it daily, monthly, or annually — basically whenever it’s convenient.
      • Insights — You’ll never have to wonder when you should share your next Instagram pic or Facebook missive. Buffer keeps track of your followers so you know when it’s the optimal time to post.

      7. Everypost

      Everypost makes it easy to curate content from across the web, schedule your posts, and integrates with Facebook, Twitter, Google+, Linkedin, Pinterest, and tumblr.

      • Analytics — You can social smarter with the analytics provided by Everypost. The dashboard is sleek and easy to use.
      • Curation — Everypost helps you curate content directly within the app — no switching around to find shareable content, design content, and post content.
      • Customization — You can maximize the potential of each post you create. This helps you reach all of your audience members, no matter what platform they are on.

      Marketing

      It’s not enough to build your website and forget it; you’ve got to be strategic about getting your brand out into the world — and in front of the eyes of your potential customers — as much as possible. Even if your business is a brick-and-mortar deal, promoting your website and digital offerings will usher local Googlers into your doors.

      8. HubSpot

      HubSpot offers a free marketing tool and WordPress plugin to help keep track of potential customers and assist you with lead conversion — that is, converting a casual browser into a committed buyer.

      • Capture — HubSpot makes it easy to grab website visitors’ email addresses by helping you create a pop-up invitation or another module. It can even pull and track information from any form submission on your website — no matter what tool or plugin.
      • Track — Along with keeping track of visitors’ email addresses and activity on your website (purchases, time spent on page, etc.), you’ll know which other websites your potential customers have visited.  
      • Convert — Analytics displayed on a simple dashboard will help you know what’s working for your site (and what isn’t) and how to target marketing emails to bring your customers back.
      • Simple — The HubSpot WordPress plugin is simple to use, even for the non-techie, and it plays nicely with other tools such as Shopify.

      Want to Keep It Simple?

      Obviously, there are several tools out there to help you effectively manage your online presence. But you don’t have to overwhelm yourself by investing in every option on the market! If you’re brand new to the web, use Remixer to get your website up quickly. Then you can add some of the other options on this list to build your brand online.



      Source link