Cosmic JS Blog Stay tuned for community news, company announcements and updates from the Cosmic JS team.

How to Build a Portfolio Platform App Using AngularJS


by Dmytro Kutsaniuk on August 31, 2017

In this tutorial I'm going to show you how to create a "Portfolio Platform" app using AngularJS, a little bit of Node, and Cosmic JS.  At the end of the tutorial we will have a platform that enables management of multiple portfolio websites.   Users can login and manage their portfolio website.  The admin can manage multiple users from the Cosmic JS Dashboard.  Let's get started.

TL;DR

Download the GitHub repo.
Check out the demo.
Install to your Cosmic JS Bucket in a few clicks.

Getting Started:

First, let’s make a new directory to build our project in and lets also make a package.json file.

mkdir cosmicapp-porfolio
cosmicapp-porfolio$ touch package.json

Now, in your package.json, copy and paste the code below:

//cosmicapp-porfolio/package.json
{
  "name": "photography-portfolio-app",
  "version": "1.0.0",
  "main": "app-server.js",
  "engines": {
    "node": "4.1.2",
    "npm": "3.5.2"
  },
  "description": "",
  "dependencies": {
    "body-parser": "^1.17.2",
    "bower": "^1.7.7",
    "buffer-to-vinyl": "^1.1.0",
    "express": "^4.13.3",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^3.1.0",
    "gulp-concat": "^2.6.0",
    "gulp-concat-css": "^2.2.0",
    "gulp-env": "^0.4.0",
    "gulp-minify-css": "^1.2.4",
    "gulp-ng-config": "^1.4.0",
    "gulp-npm-script-sync": "^1.1.0",
    "gulp-webserver": "^0.9.1",
    "http-server": "^0.9.0",
    "stripe": "^4.22.0",
    "wiredep": "^3.0.0"
  },
  "scripts": {
    "postinstall": "bower install && gulp config && gulp js",
    "start": "npm run production",
    "production": "node app-server.js",
    "gulp": "gulp"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "gulp-npm-script-sync": "^1.1.0",
    "gulp-remote-src": "^0.4.2"
  }
}
            

Second, let’s make a bower.json file.

cosmicapp-porfolio$ touch bower.json

Now, in your bower.json, copy and paste the code below:

//cosmicapp-porfolio/bower.json
{
  "name": "photography-portfolio-app",
  "description": "Photography Portfolio App",
  "version": "0.0.0",
  "homepage": "https://github.com/kutsaniuk/cosmicapp-porfolio",
  "license": "MIT",
  "private": true,
  "dependencies": {
    "angular": "~1.5.x",
    "angular-mocks": "~1.5.x",
    "angular-bootstrap": "~1.1.x",
    "angular-cookies": "~1.5.x",
    "angular-route": "~1.5.x",
    "angular-ui-router": "0.2.x",
    "angular-resource": "1.5.x",
    "angular-animate": "~1.5.x",
    "ng-dialog": "0.6.1",
    "bootstrap": "3.3.x",
    "cr-acl": "",
    "angular-chosen-localytics": "*",
    "bootstrap-chosen": "*",
    "ng-flow": "^2.7.4",
    "angular-mask": "*",
    "checklist-model": "0.9.0",
    "angular-ui-notification": "^0.2.0",
    "angular-ui-calendar": "^1.0.2",
    "angular-ui-switch": "^0.1.1",
    "ng-scrollbars": "^0.0.11",
    "jquery.scrollbar": "*",
    "angular-flash-alert": "^2.4.0",
    "components-font-awesome": "^4.7.0",
    "angular-loading-bar": "^0.9.0",
    "angular-environment": "^1.0.8",
    "angular-dragdrop": "",
    "angular-sanitize": "1.5.*",
    "angular-drag-and-drop-directives": "",
    "jquery-ui-touch-punch": "",
    "angular-touch":"1.5.*",
    "textAngular":"1.5.*"
  },
  "resolutions": {
    "angular": "~1.5.x"
  },
  "devDependencies": {
    "cr-acl": "^0.5.0"
  }
}
            

Config app server:

cosmicapp-porfolio$ touch app-server.js
//cosmicapp-porfolio/app-server.js
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
app.set('port', process.env.PORT || 3000)
app.use(express.static(__dirname))
app.use(bodyParser.json())
var http = require('http').Server(app)
// Route
app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html');
})
http.listen(app.get('port'), () => {
  console.log('Photography Portfolio App listening on ' + app.get('port'))
})
            

What we're installing and why:

  1. We're going to use the AngularJS framework to build Single-page application
  2. We're installing angular-ui-router for create multi views.
  3. We are going to use gulp for build all js and css files into one file.

Building our app:

Now we're going to build out our file structure a bit more so that we can organize our angular modules and js files. This is what our cosmicapp-porfolio directory should look like:

cosmicapp-porfolio
|----app
|       |----auth
|                 |----auth.ctrl.js
|                 |----auth.service.js
|       |----config
|                 |----config.js
|       |----portfolio
|                 |----about
|                           |----portfolio.about.mdl.js
|                 |----contact
|                           |----portfolio.contact.mdl.js
|                 |----intro
|                           |----portfolio.intro.mdl.js
|                 |----projects
|                           |----add
|                                   |----portfolio.projects.add.ctrl.js
|                           |----edit
|                                   |----portfolio.projects.edit.ctrl.js
|                           |----portfolio.projects.mdl.js
|                 |----settings
|                           |----portfolio.settings.ctrl.js
|                           |----portfolio.settings.mdl.js
|                 |----portfolio.ctrl.js
|                 |----portfolio.mdl.js
|                 |----portfolio.service.js
|       |----user
|                 |----user.service.js
|       |----main.mdl.js
|----dist
|       |----css
|       |----img
|       |----js
|----css
|----views
|----gulpfile.js
|----app-server.js
|----bower.json
|----package.json
            

Now we we will set up our index.html. Copy and paste the following code into your index.html file:

<!DOCTYPE html>
<html lang="en" ng-app="main">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Photography Portfolio App</title>

    <!-- bower:css -->
    <!-- endbower -->

    <!-- Bootstrap Core CSS -->
    <link href="bower_components/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">

    <!-- Custom CSS -->

    <link href="dist/css/main.min.css" rel="stylesheet">


    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->

</head>
<body>

<div ui-view></div>

<!-- bower:js -->
<!-- endbower -->

<script src="dist/js/main.js"></script>
</body>
</html>
            

Here, we are going to target our "root" view to place our angular modules in later. The main.js file located in our dist directory is what our gulpfile.js file will spit out after bundling all of our angular modules Now, set up our gulpfile.js file to bundle all of our js files and export that bundle file to our dist directory. Copy the following code into your gulpfile.js file:

//cosmicapp-porfolio/gulpfile.js
'use strict';

'use strict';

var gulp = require('gulp'),
    webserver = require('gulp-webserver'),
    minifyCSS = require('gulp-minify-css'),
    concat = require('gulp-concat'),
    wiredep = require('wiredep').stream,
    gulpNgConfig = require('gulp-ng-config'),
    autoprefixer = require('gulp-autoprefixer'),
    b2v = require('buffer-to-vinyl'),
    sync = require('gulp-npm-script-sync');

sync(gulp);

gulp.task('css', function () {
  return gulp.src('css/**/*.css')
    .pipe(minifyCSS())
    .pipe(concat('main.min.css'))
    .pipe(autoprefixer())
    .pipe(gulp.dest('dist/css'));
});

gulp.task('js', function() {
    return gulp.src('app/**/**/*.js')
        .pipe(concat('main.js'))
        .pipe(gulp.dest('dist/js/'));
});

gulp.task('config', function () {
    const json = JSON.stringify({
        BUCKET_SLUG: process.env.COSMIC_BUCKET,
        MEDIA_URL: 'https://api.cosmicjs.com/v1/' + process.env.COSMIC_BUCKET + '/media',
        URL: 'https://api.cosmicjs.com/v1/',
        READ_KEY: process.env.COSMIC_READ_KEY || '',
        WRITE_KEY: process.env.COSMIC_WRITE_KEY || '',
        DEFAULT_IMAGE: process.env.DEFAULT_IMAGE || ''
    });
    return b2v.stream(new Buffer(json), 'config.js')
        .pipe(gulpNgConfig('config'))
        .pipe(gulp.dest('app/config'));
});

gulp.task('default', function () {
  gulp.watch('css/**/*.css', ['css']);
  gulp.watch('app/**/**/*.js', ['js']);
  gulp.watch('bower.json', ['bower']);
});

gulp.task('bower', function () {
  gulp.src('index.html')
    .pipe(wiredep({
      directory: 'bower_components'
    }))
    .pipe(gulp.dest(''));
});
            

After that we can create main module. Copy and paste the following code into your main.mdl.js file:

(function () {
    'use strict';

    angular
        .module('main', [
            'ui.router',
            'ui.bootstrap',
            'ngMask',
            'ngCookies',
            'ngRoute',
            'ngDialog',
            'cr.acl',
            'ui-notification',
            'ngFlash',
            'ngAnimate',
            'textAngular',
            'flow',
            'angular-loading-bar',
            'ngDragDrop',
            'ngSanitize',
            'ngTouch',

            'portfolio',

            'config'
        ])
        .config(config)
        .run(run);

    config.$inject = ['$stateProvider', '$urlRouterProvider', 'cfpLoadingBarProvider', 'NotificationProvider', '$locationProvider'];
    function config($stateProvider, $urlRouterProvider, cfpLoadingBarProvider, NotificationProvider, $locationProvider) {
        cfpLoadingBarProvider.includeSpinner = false;

        NotificationProvider.setOptions({
            startTop: 25,
            startRight: 25,
            verticalSpacing: 20,
            horizontalSpacing: 20,
            positionX: 'right',
            positionY: 'bottom'
        });

        $urlRouterProvider.otherwise(function ($injector) {
            var $state = $injector.get("$state");
            $state.go('login');
        });

        $stateProvider
            .state('blog', {
                url: '/blog',
                templateUrl: '../blog.html'
            })
            .state('login', {
                url: '/login',
                templateUrl: '../views/auth/login.html',
                controller: 'AuthCtrl as auth',
                onEnter: ['AuthService', 'crAcl', function(AuthService, crAcl) {
                    AuthService.clearCredentials();
                    crAcl.setRole();
                }],
                data: {
                    is_granted: ['ROLE_GUEST']
                }
            })
            .state('register', {
                url: '/register',
                templateUrl: '../views/auth/register.html',
                controller: 'AuthCtrl as auth',
                onEnter: ['AuthService', 'crAcl', function(AuthService, crAcl) {
                    AuthService.clearCredentials();
                    crAcl.setRole();
                }],
                data: {
                    is_granted: ['ROLE_GUEST']
                }
            });

    }

    run.$inject = ['$rootScope', '$cookieStore', '$state', 'crAcl'];
    function run($rootScope, $cookieStore, $state, crAcl) {

        $rootScope.globals = $cookieStore.get('globals') || {};

        crAcl
            .setInheritanceRoles({
                'ROLE_ADMIN': ['ROLE_ADMIN', 'ROLE_GUEST'],
                'ROLE_USER': ['ROLE_USER', 'ROLE_GUEST'],
                'ROLE_GUEST': ['ROLE_GUEST']
            });

        crAcl
            .setRedirect('login');

        if ($rootScope.globals.currentUser) {
            crAcl.setRole($rootScope.globals.currentUser.role);
            $state.go('portfolio.intro');
        }
        else {
            crAcl.setRole();
        }

    }

})();
             

Now we we will set up our Auth Controller. Copy and paste the following code into your auth.ctrl.js file:

(function () {
    'use strict';

    angular
        .module('main')
        .controller('AuthCtrl', AuthCtrl);

    function AuthCtrl(crAcl, $state, AuthService, Flash, $log) {
        var vm = this;

        vm.login = login;
        vm.register = register;

        vm.loginForm = null;

        vm.credentials = {};
        vm.user = {};

        function login(credentials) {
            function success(response) {
                function success(response) {
                    if (response.data.status !== 'empty') {
                        var currentUser = response.data.objects[0];

                        crAcl.setRole('ROLE_USER');
                        AuthService.setCredentials({
                            slug: currentUser.slug,
                            first_name: currentUser.metadata.first_name,
                            last_name: currentUser.metadata.last_name,
                            email: currentUser.metadata.email,
                            role: 'ROLE_USER'
                        });
                        $state.go('portfolio.intro', {slug: currentUser.slug});
                    }
                    else
                        Flash.create('danger', 'Incorrect username or password');
                }

                function failed(response) {
                    $log.error(response);
                }

                if (response.data.status !== 'empty')
                    AuthService
                        .checkPassword(credentials)
                        .then(success, failed);
                else
                    Flash.create('danger', 'Incorrect username or password');

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            if (vm.loginForm.$valid)
                AuthService
                    .checkUsername(credentials)
                    .then(success, failed);
        }

        function register(credentials) {
            function success(response) {
                login(credentials);

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            if (vm.loginForm.$valid)
                AuthService
                    .register(credentials)
                    .then(success, failed);
        }

    }
})();
            

Create Auth Service, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('main')
        .service('AuthService', function ($http,
                                          $cookieStore,
                                          $q,
                                          $rootScope,
                                          URL, BUCKET_SLUG, READ_KEY, WRITE_KEY) {
            var authService = this;
            $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            authService.checkUsername = function (credentials) {
                return $http.get(URL + BUCKET_SLUG + '/object-type/users/search', {
                    params: {
                        metafield_key: 'email',
                        metafield_value_has: credentials.email,
                        limit: 1,
                        read_key: READ_KEY
                    }
                });
            };
            authService.checkPassword = function (credentials) {
                return $http.get(URL + BUCKET_SLUG + '/object-type/users/search', {
                    ignoreLoadingBar: true,
                    params: {
                        metafield_key: 'password',
                        metafield_value: credentials.password,
                        limit: 1,
                        read_key: READ_KEY
                    }
                });
            };
            this.register = function (user) {
                return $http.post(URL + BUCKET_SLUG + '/add-object', {
                        title: user.first_name + ' ' + user.last_name,
                        type_slug: 'users',
                        slug: user.username,
                        metafields: [
                            {
                                key: "first_name",
                                type: "text",
                                value: user.first_name
                            },
                            {
                                key: "last_name",
                                type: "text",
                                value: user.last_name
                            },
                            {
                                key: "email",
                                type: "text",
                                value: user.email
                            },
                            {
                                key: "password",
                                type: "text",
                                value: user.password
                            },
                            {
                                key: "intro",
                                type: "html-textarea",
                                value: null
                            },
                            {
                                key: "about",
                                type: "html-textarea",
                                value: null
                            },
                            {
                                key: "contact",
                                type: "html-textarea",
                                value: null
                            },
                            {
                                key: "projects",
                                type: "objects",
                                objects: [],
                                object_type: "projects",
                                value: null
                            }
                        ],

                        write_key: WRITE_KEY
                    }, { ignoreLoadingBar: false });
            };
            authService.setCredentials = function (user) {
                $rootScope.globals = {
                    currentUser: user
                };

                $cookieStore.put('globals', $rootScope.globals);
            };
            authService.clearCredentials = function () {
                var deferred = $q.defer();
                $cookieStore.remove('globals');

                if (!$cookieStore.get('globals')) {
                    $rootScope.globals = {};
                    deferred.resolve('Credentials clear success');
                } else {
                    deferred.reject('Can\'t clear credentials');
                }

                return deferred.promise;
            };
        });
})();
            

What's going on here:

  1. We are using the ui-router for config routes.
  2. We created Auth Service for our asynchronous calls to our Cosmic JS API.
  3. We created Auth Controller for checking credentials and registration users.

Create User Service for get and update User, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('main')
        .service('UserService', function ($http,
                                          $cookieStore,
                                          $q,
                                          $rootScope,
                                          URL, BUCKET_SLUG, READ_KEY, WRITE_KEY) {
            $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            this.getCurrentUser = function (ignoreLoadingBar) {
                return $http.get(URL + BUCKET_SLUG + '/object/' + $rootScope.globals.currentUser.slug, {
                    ignoreLoadingBar: ignoreLoadingBar,
                    params: {
                        read_key: READ_KEY
                    }
                });
            };
            this.getUser = function (slug, ignoreLoadingBar) {
                return $http.get(URL + BUCKET_SLUG + '/object/' + slug, {
                    ignoreLoadingBar: ignoreLoadingBar,
                    params: {
                        read_key: READ_KEY
                    }
                });
            };
            this.updateUser = function (user) {
                user.write_key = WRITE_KEY;

                return $http.put(URL + BUCKET_SLUG + '/edit-object', user, {
                    ignoreLoadingBar: false
                });
            };

        });
})();
            

Create Portfolio Service for get and update, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('main')
        .service('PortfolioService', function ($http,
                                          $cookieStore,
                                          $q,
                                          $rootScope,
                                          URL, BUCKET_SLUG, READ_KEY, WRITE_KEY, MEDIA_URL) {

            $http.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

            this.getPortfolioBySlug = function (slug) {
                return $http.get(URL + BUCKET_SLUG + '/object/' + slug, {
                    params: {
                        read_key: READ_KEY
                    }
                });
            };

            this.updatePortfolio = function (portfolio) {
                portfolio.write_key = WRITE_KEY;

                return $http.put(URL + BUCKET_SLUG + '/edit-object', portfolio);
            };

            this.upload = function (file) {
                var fd = new FormData();
                fd.append('media', file);
                fd.append('write_key', WRITE_KEY);

                var defer = $q.defer();

                var xhttp = new XMLHttpRequest();

                xhttp.upload.addEventListener("progress",function (e) {
                    defer.notify(parseInt(e.loaded * 100 / e.total));
                });
                xhttp.upload.addEventListener("error",function (e) {
                    defer.reject(e);
                });

                xhttp.onreadystatechange = function() {
                    if (xhttp.readyState === 4) {
                        defer.resolve(JSON.parse(xhttp.response)); //Outputs a DOMString by default
                    }
                };

                xhttp.open("post", MEDIA_URL, true);

                xhttp.send(fd);

                return defer.promise;
            }
        });
})();
            

Create Portfolio Controller for update and add, edit projects, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('main')
        .controller('PortfolioCtrl', PortfolioCtrl);

    function PortfolioCtrl($rootScope, $stateParams, $sce, $scope, $state, ngDialog, AuthService, UserService, PortfolioService, $log) {
        var vm = this;

        getPortfolio();

        vm.currentUser = $rootScope.globals.currentUser ? $rootScope.globals.currentUser : getUser();

        vm.logout = logout;
        vm.updatePortfolio = updatePortfolio;
        vm.openAddProjectDialog = openAddProjectDialog;
        vm.openEditProjectDialog = openEditProjectDialog;

        vm.portfolio = {};

        vm.toolbarEditor = [
            ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'bold', 'italics', 'underline', 'justifyLeft', 'justifyCenter', 'justifyRight']
        ];

        function chunk(arr, size) {
            var newArr = [];
            for (var i = 0; i < arr.length; i += size) {
                newArr.push(arr.slice(i, i + size));
            }
            return newArr;
        }

        function getUser() {
            function success(response) {
                var currentUser = response.data.object;

                vm.currentUser = {
                    slug: currentUser.slug,
                    first_name: currentUser.metadata.first_name,
                    last_name: currentUser.metadata.last_name,
                    email: currentUser.metadata.email
                };
            }

            function failed(response) {
                $log.error(response);
            }

            UserService
                .getUser($stateParams.slug, true)
                .then(success, failed);
        }

        function getPortfolio() {
            function success(response) {
                vm.portfolio = response.data.object;

                if (Array.isArray(vm.portfolio.metadata.projects))
                    vm.projectsChunk = chunk(vm.portfolio.metadata.projects, 2);
                else
                    vm.projectsChunk = [];

                vm.contact = $sce.trustAsResourceUrl('mailto:' + vm.currentUser.email);

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            PortfolioService
                .getPortfolioBySlug($stateParams.slug)
                .then(success, failed);
        }

        function updatePortfolio() {
            function success(response) {
                getPortfolio();

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            PortfolioService
                .updatePortfolio(vm.portfolio)
                .then(success, failed);
        }

        function openAddProjectDialog(slug) {

            var options = {
                templateUrl: '../views/portfolio/portfolio.project.edit.html',
                showClose: true,
                controller: 'PortfolioProjectsAddCtrl as vm'
            };

            ngDialog.open(options).closePromise.finally(function () {
                getPortfolio();
            });
        }

        function openEditProjectDialog(slug) {

            var options = {
                templateUrl: '../views/portfolio/portfolio.project.edit.html',
                showClose: true,
                controller: 'PortfolioProjectsEditCtrl as vm',
                data: {
                    slug: slug
                }
            };

            ngDialog.open(options).closePromise.finally(function () {
                getPortfolio();
            });
        }

        function logout() {
            function success(response) {
                $state.go('auth');

                $log.info(response);
            }

            function failed(response) {
                $log.error(response);
            }

            AuthService
                .clearCredentials()
                .then(success, failed);
        }

        $scope.state = $state;

    }
})();
            

Create Portfolio Module, copy and paste the code below:

(function () {
    'use strict';

    angular
        .module('portfolio', [
            'portfolio.intro',
            'portfolio.projects',
            'portfolio.about',
            'portfolio.contact',
            'portfolio.settings'
        ])
        .config(config);

    config.$inject = ['$stateProvider'];
    function config($stateProvider) {

        $stateProvider
            .state('portfolio', {
                url: '/:slug/',
                abstract: true,
                templateUrl: '../views/portfolio/portfolio.html',
                controller: 'PortfolioCtrl as vm',
                data: {
                    is_granted: ['ROLE_USER', 'ROLE_GUEST']
                }
            });
    }
})();
            

What's going on here:

  1. We created Portfolio Service for get and update.
  2. We created Portfolio Controller for update and add, edit project.
  3. We created Portfolio Module.

Create Author Controller for getting information of author, copy and paste the code below:

Conclusion:

We were able to consume the Cosmic JS API with our actions and dispatcher functions. I hope you enjoyed this tutorial as much as I did, if you have any questions reach out to us on Twitter and join our community on Slack.

You may also like


In this article I’m going to show you how to quickly and easily create a cross-platform blog that will connect your content across Web, iOS and Android.  With content powered but Cosmic JS, you will be able to edit your content once and deliver the update simultaneously across all devices and platforms.  This is powerful stuff.  Let’s get started.

In this tutorial I'm going to show you how to create an "Events" app using a little bit of Node, Angular JS and Cosmic JS. For the sake of understanding how to consume Restful API’s, this tutorial will show how to make simple AJAX requests to the Cosmic JS API in order to retrieve, update, and delete data in our Cosmic JS Buckets.

In this short tutorial I'll show you how easy it is to add a CMS to a simple browser app using the Cosmic JS API.   It will literally take you 2 minutes to build.  Our app will consist of just 3 files:

1. index.html
2. app.js
2. package.json

Let's get started shall we?  In your terminal of choice run the following commands:

mkdir easy-browser-example
cd easy-browser-example
npm install cosmicjs
npm install browserify -g

Now let's build our index.html file.  Run the following command in your terminal:

vim index.html

Add the following to our index.html file:

<!DOCTYPE html>
<html>
<head>
  <title>Cosmic JS Easy Browser Example</title>
</head>
<body>
  <h1 id="title">If you see this, something isn't working...</h1>
  <div id="content"></div>
  <div id="metafields"></div>
  <script src="app.browser.js"></script>
</body>
</html>

We could just as easily use jQuery and Ajax to render our content, but for this example we will use the official Cosmic JS Node.js package. Now create a file called app.js:

vim app.js

And add the following to app.js:

// app.js
var Cosmic = require('cosmicjs')
const bucket = { slug: 'easy-browser-example' }
const object = { slug: 'home' }
Cosmic.getObject({ bucket }, object, (err, res) => { var object = res.object
  document.getElementById('title').innerHTML = object.title
  document.getElementById('content').innerHTML = object.content
  document.getElementById('metafields').innerHTML = '<pre>' + JSON.stringify(object.metafields, null, 2) + '</pre>'
})

Notice that in our app.js file we are returning content from the Cosmic JS API, and then attaching our content to the DOM elements at "title","content" and "metafields".

Next we'll add a package.json file that will allow us to add some simple scripts to "browserify" our app.js file:

vim package.json

Add the following to a new file titled package.json:

{
  "name": "easy-browser-example",
  "main": "app.js",
  "scripts": {
    "browserify": "browserify app.js -o app.browser.js"
  }
}

Now let's run our our scripts to bundle our code into the browser.  Run the following script which will bundle the new file to app.browser.js:

npm run browserify

Now view the index.html file in your browser and you will see that the content from our example bucket "easy-browser-example" can be seen and the metafields data is rendered to a string to show you what data is available.  Taking this a step further, you can see how powerful this can be if you add React or Angular into the mix.

I hope you enjoyed this short tutorial.  If you have not already, you can sign up for a Cosmic JS account, and begin playing with this example using content from your own bucket.  Thanks and happy building!


The Repeater Metafield allows you to group Metafields into groups with the ability to “repeat” the group.  This makes building blocks of content on a page really intuitive.

The process of building your messenger bot is fairly simple the hardest part is setting up your machine to talk to talk to Facebook. That's why today I'm going to walk you through that real quick. Once it is all done you can get right on the way to creating your own bot.

With all the recent Gatsby fervor, we were delighted to get to sit down with Kyle Mathews, founder of Gatsby and a software engineer living in San Francisco. Follow Kyle on GitHub and Twitter, and enjoy the Q/A.