How To Build A Group Chat App With Vanilla JS, Twilio And Node.js

Chat is becoming an increasingly popular communication medium in both business and social contexts. Businesses use chat for customer and employee intra-company communication like with Slack, Microsoft Teams, Chanty, HubSpot Live Chat, Help Scout, etc. Most social networks and communication apps also offer chat as an option by default, like on Instagram, Facebook, Reddit, and Twitter. Other apps like Discord, Whatsapp, and Telegram are mostly chat-based, with group chats being one of their main functionalities.

While there exist numerous products to facilitate chat, you may need a custom-tailored solution for your site that fits your particular communication needs. For example, many of these products are stand-alone apps and may not be able to integrate within your own site. Having your users leave your website to chat may not be the greatest option as it can affect user experience and conversion. On the flip side, building a chat app from scratch can be a daunting and sometimes overwhelming task. However, by using APIs like Twilio Conversations you can simplify the process of creating them. These communication APIs handle group creation, adding participants, sending messages, notifications, among other important chat functions. Backend apps that use these APIs only have to handle authentication and make calls to these APIs. Front-end apps then display conversations, groups, and messages from the backend.

In this tutorial, you will learn how to create a group chat app using the Twilio Conversations API. The front end for this app will be built using HTML, CSS, and Vanilla JavaScript. It will allow users to create group chats, send invites, login, as well as send and receive messages. The backend will be a Node.js app. It will provide authentication tokens for chat invitees and manage chat creation.

Prerequisites

Before you can start this tutorial, you need to have the following:

  • Node.js installed. You’ll use it primarily for the backend app and to install dependencies in the front-end app.
    You can get it using a pre-built installer available on the Node.js downloads page.
  • A Twilio account.
    You can create one on the Twilio website at this link.
  • http-server to serve the front-end app.
    You can install it by running npm i -g http-server. You can also run it with npx http-server for one-off runs.
  • MongoDB for session storage in the backend app.
    Its installation page has a detailed guide on how to get it running.
The Backend App

To send chat messages using Twilio API, you need a conversation. Chat messages are sent and received within a conversation. The people sending the messages are called participants. A participant can only send a message within a conversation if they are added to it. Both conversations and participants are created using the Twilio API. The backend app will perform this function.

A participant needs an access token to send a message and get their subscribed conversations. The front-end portion of this project will use this access token. The backend app creates the token and sends it to the frontend. There it will be used to load conversations and messages.

Project Starter

You’ll call the backend app twilio-chat-server. A scaffolded project starter for it is available on Github. To clone the project and get the starter, run:

git clone https://github.com/zaracooper/twilio-chat-server.git
cd twilio-chat-server
git checkout starter

The backend app takes this structure:

.
├── app.js
├── config/
├── controllers/
├── package.json
├── routes/
└── utils/

To run the app, you’ll use the node index.js command.

Dependencies

The backend app needs 8 dependencies. You can install them by running:

npm i 

Here’s a list of each of the dependencies:

  • connect-mongo connects to MongoDB, which you’ll use as a session store;
  • cors handles CORS;
  • dotenv loads environment variables from the .env file that you will create in a later step;
  • express is the web framework you’ll use for the backend;
  • express-session provides middleware to handle session data;
  • http-errors helps create server errors;
  • morgan handles logging;
  • twilio creates the Twilio client, generates tokens, creates conversations, and adds participants.

Configuration

The config folder is responsible for loading configuration from environment variables. The configuration is grouped into three categories: configuration for CORS, Twilio, and the MongoDB session DB. When the environment is development, you will load config from the .env file using dotenv.

Start by creating the .env file on the terminal. This file is already added to the .gitignore file to prevent the sensitive values it contains from being checked into the repository.

touch .env

Here’s what your .env should look like:

# Session DB Config
SESSION_DB_HOST=XXXX
SESSION_DB_USER=XXXX
SESSION_DB_PASS=XXXX
SESSION_DB_PORT=XXXX
SESSION_DB_NAME=XXXX
SESSION_DB_SECRET=XXXX

# Twilio Config
TWILIO_ACCOUNT_SID=XXXX
TWILIO_AUTH_TOKEN=XXXX
TWILIO_API_KEY=XXXX
TWILIO_API_SECRET=XXXX

# CORS Client Config
CORS_CLIENT_DOMAIN=XXXX

You can learn how to create a user for your session DB from this MongoDB manual entry. Once you create a session database and a user who can write to it, you can fill the SESSION_DB_USER, SESSION_DB_PASS, and SESSION_DB_NAME values. If you’re running a local instance of MongoDB, the SESSION_DB_HOST would be localhost, and the SESSION_DB_PORT usually is 27017. The SESSION_DB_SECRET is used by express-session to sign the session ID cookie, and it can be any secret string you set.

In the next step, you will get credentials from the Twilio Console. The credentials should be assigned to the variables with the TWILIO_ prefix. During local development, the front-end client will run on http://localhost:3000. So, you can use this value for the CORS_CLIENT_DOMAIN environment variable.

Add the following code to config/index.js to load environment variables.

import dotenv from 'dotenv';

if (process.env.NODE_ENV == 'development') {
    dotenv.config();
}

const corsClient = {
    domain: process.env.CORS_CLIENT_DOMAIN
};

const sessionDB = {
    host: process.env.SESSION_DB_HOST,
    user: process.env.SESSION_DB_USER,
    pass: process.env.SESSION_DB_PASS,
    port: process.env.SESSION_DB_PORT,
    name: process.env.SESSION_DB_NAME,
    secret: process.env.SESSION_DB_SECRET
};

const twilioConfig = {
    accountSid: process.env.TWILIO_ACCOUNT_SID,
    authToken: process.env.TWILIO_AUTH_TOKEN,
    apiKey: process.env.TWILIO_API_KEY,
    apiSecret: process.env.TWILIO_API_SECRET
};

const port = process.env.PORT || '8000';

export { corsClient, port, sessionDB, twilioConfig };

The environment variables are grouped into categories based on what they do. Each of the configuration categories has its own object variable, and they are all exported for use in other parts of the app.

Getting Twilio Credentials From the Console

To build this project, you’ll need four different Twilio credentials: an Account SID, an Auth Token, an API key, and an API secret. In the console, on the General Settings page, scroll down to the API Credentials section. This is where you will find your Account SID and Auth Token.

To get an API Key and Secret, go to the API Keys page. You can see it in the screenshot below. Click the + button to go to the New API Key page.

On this page, add a key name and leave the KEY TYPE as Standard, then click Create API Key. Copy the API key and secret. You will add all these credentials in a .env file as you shall see in subsequent steps.

Utils

The backend app needs two utility functions. One will create a token, and the other will wrap async controllers and handle errors for them.

In utils/token.js, add the following code to create a function called createToken that will generate Twilio access tokens:

import { twilioConfig } from '../config/index.js';
import twilio from 'twilio';

function createToken(username, serviceSid) {
    const AccessToken = twilio.jwt.AccessToken;
    const ChatGrant = AccessToken.ChatGrant;

    const token = new AccessToken(
        twilioConfig.accountSid,
        twilioConfig.apiKey,
        twilioConfig.apiSecret,
        { identity: username }
    );

    const chatGrant = new ChatGrant({
        serviceSid: serviceSid,
    });

    token.addGrant(chatGrant);

    return token.toJwt();
}

In this function, you generate access tokens using your Account SID, API key, and API secret. You can optionally supply a unique identity which could be a username, email, etc. After creating a token, you have to add a chat grant to it. The chat grant can take a conversation service ID among other optional values. Lastly, you’ll convert the token to a JWT and return it.

The utils/controller.js file contains an asyncWrapper function that wraps async controller functions and catches any errors they throw. Paste the following code into this file:

function asyncWrapper(controller) {
    return (req, res, next) => Promise.resolve(controller(req, res, next)).catch(next);
}

export { asyncWrapper, createToken };

Controllers

The backend app has four controllers: two for authentication and two for handling conversations. The first auth controller creates a token, and the second deletes it. One of the conversations controllers creates new conversations, while the other adds participants to existing conversations.

Conversation Controllers

In the controllers/conversations.js file, add these imports and code for the StartConversation controller:

import { twilioConfig } from '../config/index.js';
import { createToken } from '../utils/token.js';
import twilio from 'twilio';

async function StartConversation(req, res, next) {
    const client = twilio(twilioConfig.accountSid, twilioConfig.authToken);

    const { conversationTitle, username } = req.body;

    try {
        if (conversationTitle && username) {
            const conversation = await client.conversations.conversations
                .create({ friendlyName: conversationTitle });

            req.session.token = createToken(username, conversation.chatServiceSid);
            req.session.username = username;

            const participant = await client.conversations.conversations(conversation.sid)
                .participants.create({ identity: username })

            res.send({ conversation, participant });
        } else {
            next({ message: 'Missing conversation title or username' });
        }
    }
    catch (error) {
        next({ error, message: 'There was a problem creating your conversation' });
    }
}

The StartConversation controller first creates a Twilio client using your twilioConfig.accountSid and twilioConfig.authToken which you get from config/index.js.

Next, it creates a conversation. It needs a conversation title for this, which it gets from the request body. A user has to be added to a conversation before they can participate in it. A participant cannot send a message without an access token. So, it generates an access token using the username provided in the request body and the conversation.chatServiceSid. Then the user identified by the username is added to the conversation. The controller completes by responding with the newly created conversation and participant.

Next, you need to create the AddParticipant controller. To do this, add the following code below what you just added in the controllers/conversations.js file above:

async function AddParticipant(req, res, next) {
    const client = twilio(twilioConfig.accountSid, twilioConfig.authToken);

    const { username } = req.body;
    const conversationSid = req.params.id;

    try {
        const conversation = await client.conversations.conversations
            .get(conversationSid).fetch();

        if (username && conversationSid) {
            req.session.token = createToken(username, conversation.chatServiceSid);
            req.session.username = username;

            const participant = await client.conversations.conversations(conversationSid)
                .participants.create({ identity: username })

            res.send({ conversation, participant });
        } else {
            next({ message: 'Missing username or conversation Sid' });
        }
    } catch (error) {
        next({ error, message: 'There was a problem adding a participant' });
    }
}

export { AddParticipant, StartConversation };

The AddParticipant controller adds new participants to already existing conversations. Using the conversationSid provided as a route parameter, it fetches the conversation. It then creates a token for the user and adds them to the conversation using their username from the request body. Lastly, it sends the conversation and participant as a response.

Auth Controllers

The two controllers in controllers/auth.js are called GetToken and DeleteToken. Add them to the file by copying and pasting this code:

function GetToken(req, res, next) {
    if (req.session.token) {
        res.send({ token: req.session.token, username: req.session.username });
    } else {
        next({ status: 404, message: 'Token not set' });
    }
}

function DeleteToken(req, res, _next) {
    delete req.session.token;
    delete req.session.username;

    res.send({ message: 'Session destroyed' });
}

export { DeleteToken, GetToken };

The GetToken controller retrieves the token and username from the session if they exist and returns them as a response. DeleteToken deletes the session.

Routes

The routes folder has three files: index.js, conversations.js, and auth.js.

Add these auth routes to the routes/auth.js file by adding this code:

import { Router } from 'express';

import { DeleteToken, GetToken } from '../controllers/auth.js';

var router = Router();

router.get('/', GetToken);
router.delete('/', DeleteToken);

export default router;

The GET route at the / path returns a token while the DELETE route deletes a token.

Next, copy and paste the following code to the routes/conversations.js file:

import { Router } from 'express';
import { AddParticipant, StartConversation } from '../controllers/conversations.js';
import { asyncWrapper } from '../utils/controller.js';

var router = Router();

router.post('/', asyncWrapper(StartConversation));
router.post('/:id/participants', asyncWrapper(AddParticipant));

export default router;

In this file, the conversations router is created. A POST route for creating conversations with the path / and another POST route for adding participants with the path /:id/participants are added to the router.

Lastly, add the following code to your new routes/index.js file.

import { Router } from 'express';

import authRouter from './auth.js';
import conversationRouter from './conversations.js';

var router = Router();

router.use('/auth/token', authRouter);
router.use('/api/conversations', conversationRouter);

export default router;

By adding the conversation and auth routers here, you are making them available at /api/conversations and /auth/token to the main router respectively. The router is then exported.

The Backend App

Now it’s time to put the backend pieces together. Open the index.js file in your text editor and paste in the following code:

import cors from 'cors';
import createError from 'http-errors';
import express, { json, urlencoded } from 'express';
import logger from 'morgan';
import session from 'express-session';
import store from 'connect-mongo';

import { corsClient, port, sessionDB } from './config/index.js';

import router from './routes/index.js';

var app = express();

app.use(logger('dev'));
app.use(json());
app.use(urlencoded({ extended: false }));

app.use(cors({
    origin: corsClient.domain,
    credentials: true,
    methods: ['GET', 'POST', 'DELETE'],
    maxAge: 3600 * 1000,
    allowedHeaders: ['Content-Type', 'Range'],
    exposedHeaders: ['Accept-Ranges', 'Content-Encoding', 'Content-Length', 'Content-Range']
}));
app.options('*', cors());

app.use(session({
    store: store.create({
        mongoUrl: mongodb://${sessionDB.user}:${sessionDB.pass}@${sessionDB.host}:${sessionDB.port}/${sessionDB.name},
        mongoOptions: { useUnifiedTopology: true },
        collectionName: 'sessions'
    }),
    secret: sessionDB.secret,
    cookie: {
        maxAge: 3600 * 1000,
        sameSite: 'strict'
    },
    name: 'twilio.sid',
    resave: false,
    saveUninitialized: true
}));

app.use('/', router);

app.use(function (_req, _res, next) {
    next(createError(404, 'Route does not exist.'));
});

app.use(function (err, _req, res, _next) {
    res.status(err.status || 500).send(err);
});

app.listen(port);

This file starts off by creating the express app. It then sets up JSON and URL-encoded payload parsing and adds the logging middleware. Next, it sets up CORS and the session handling. As mentioned earlier, MongoDB is used as the session store.

After all that is set up, it then adds the router created in the earlier step before configuring error handling. Lastly, it makes the app listen to and accept connections at the port specified in the .env file. If you haven’t set the port, the app will listen on port 8000.

Once you’re finished creating the backend app, make sure MongoDB is running and start it by running this command on the terminal:

NODE_ENV=development npm start

You pass the NODE_ENV=development variable, so that configuration is loaded from the local .env file.

The Front-end

The front-end portion of this project serves a couple of functions. It allows users to create conversations, see the list of conversations they are a part of, invite others to conversations they created, and send messages within conversations. These roles are achieved by four pages:

  • a conversations page,
  • a chat page,
  • an error page,
  • a login page.

You’ll call the front-end app twilio-chat-app. A scaffolded starter exists for it on Github. To clone the project and get the starter, run:

git clone https://github.com/zaracooper/twilio-vanilla-js-chat-app.git
cd twilio-vanilla-js-chat-app
git checkout starter

The app takes this structure:

.
├── index.html
├── pages
│   ├── chat.html
│   ├── conversation.html
│   ├── error.html
│   └── login.html
├── scripts
│   ├── chat.js
│   ├── conversation.js
│   └── login.js
└── styles
    ├── chat.css
    ├── main.css
    └── simple-page.css

The styling and HTML markup have already been added for each of the pages in the starter. This section will only cover the scripts you have to add.

Dependencies

The app has two dependencies: axios and @twilio/conversations. You’ll use axios to make requests to the backend app and @twilio/conversations to send and fetch messages and conversations in scripts. You can install them on the terminal by running:

npm i

The Index Page

This page serves as a landing page for the app. You can find the markup for this page (index.html) here. It uses two CSS stylesheets: styles/main.css which all pages use and styles/simple-page.css which smaller, less complicated pages use.

You can find the contents of these stylesheets linked in the earlier paragraph. Here is a screenshot of what this page will look like:

The Error Page

This page is shown when an error occurs. The contents of pages/error.html can be found here. If an error occurs, a user can click the button to go to the home page. There, they can try what they were attempting again.

The Conversations Page

On this page, a user provides the title of a conversation to be created and their username to a form.

The contents of pages/conversation.html can be found here. Add the following code to the scripts/conversation.js file:

window.twilioChat = window.twilioChat || {};

function createConversation() {
    let convoForm = document.getElementById('convoForm');
    let formData = new FormData(convoForm);

    let body = Object.fromEntries(formData.entries()) || {};

    let submitBtn = document.getElementById('submitConvo');
    submitBtn.innerText = "Creating..."
    submitBtn.disabled = true;
    submitBtn.style.cursor = 'wait';

    axios.request({
        url: '/api/conversations',
        baseURL: 'http://localhost:8000',
        method: 'post',
        withCredentials: true,
        data: body
    })
        .then(() => {
            window.twilioChat.username = body.username;
            location.href = '/pages/chat.html';
        })
        .catch(() => {
            location.href = '/pages/error.html';
        });
}

When a user clicks the Submit button, the createConversation function is called. In it, the contents of the form are collected and used in the body of a POST request made to http://localhost:8000/api/conversations/ in the backend.

You will use axios to make the request. If the request is successful, a conversation is created and the user is added to it. The user will then be redirected to the chat page where they can send messages in the conversation.

Below is a screenshot of the conversations page:

The Chat Page

On this page, a user will view a list of conversations they are part of and send messages to them. You can find the markup for pages/chat.html here and the styling for styles/chat.css here.

The scripts/chat.js file starts out by defining a namespace twilioDemo.

window.twilioChat = window.twilioChat || {};

Add the initClient function below. It is responsible for initializing the Twilio client and loading conversations.

async function initClient() {
    try {
        const response = await axios.request({
            url: '/auth/token',
            baseURL: 'http://localhost:8000',
            method: 'GETget',
            withCredentials: true
        });

        window.twilioChat.username = response.data.username;
        window.twilioChat.client = await Twilio.Conversations.Client.create(response.data.token);

        let conversations = await window.twilioChat.client.getSubscribedConversations();

        let conversationCont, conversationName;

        const sideNav = document.getElementById('side-nav');
        sideNav.removeChild(document.getElementById('loading-msg'));

        for (let conv of conversations.items) {
            conversationCont = document.createElement('button');
            conversationCont.classList.add('conversation');
            conversationCont.id = conv.sid;
            conversationCont.value = conv.sid;
            conversationCont.onclick = async () => {
                await setConversation(conv.sid, conv.channelState.friendlyName);
            };

            conversationName = document.createElement('h3');
            conversationName.innerText = 💬 ${conv.channelState.friendlyName};

            conversationCont.appendChild(conversationName);
            sideNav.appendChild(conversationCont);
        }
    }
    catch {
        location.href = '/pages/error.html';
    }
};

When the page loads, initClient fetches the user’s access token from the backend, then uses it to initialise the client. Once the client is initialised, it’s used to fetch all the conversations the user is subscribed to. After that, the conversations are loaded onto the side-nav. In case any error occurs, the user is sent to the error page.

The setConversion function loads a single conversation. Copy and paste the code below in the file to add it:

async function setConversation(sid, name) {
    try {
        window.twilioChat.selectedConvSid = sid;

        document.getElementById('chat-title').innerText = '+ ' + name;

        document.getElementById('loading-chat').style.display = 'flex';
        document.getElementById('messages').style.display = 'none';

        let submitButton = document.getElementById('submitMessage')
        submitButton.disabled = true;

        let inviteButton = document.getElementById('invite-button')
        inviteButton.disabled = true;

        window.twilioChat.selectedConversation = await window.twilioChat.client.getConversationBySid(window.twilioChat.selectedConvSid);

        const messages = await window.twilioChat.selectedConversation.getMessages();

        addMessagesToChatArea(messages.items, true);

        window.twilioChat.selectedConversation.on('messageAdded', msg => addMessagesToChatArea([msg], false));

        submitButton.disabled = false;
        inviteButton.disabled = false;
    } catch {
        showError('loading the conversation you selected');
    }
};

When a user clicks on a particular conversation, setConversation is called. This function receives the conversation SID and name and uses the SID to fetch the conversation and its messages. The messages are then added to the chat area. Lastly, a listener is added to watch for new messages added to the conversation. These new messages are appended to the chat area when they are received. In case any errors occur, an error message is displayed.

This is a screenshot of the chat page:

Next, you’ll add the addMessagedToChatArea function which loads conversation messages.

function addMessagesToChatArea(messages, clearMessages) {
    let cont, msgCont, msgAuthor, timestamp;

    const chatArea = document.getElementById('messages');

    if (clearMessages) {
        document.getElementById('loading-chat').style.display = 'none';
        chatArea.style.display = 'flex';
        chatArea.replaceChildren();
    }

    for (const msg of messages) {
        cont = document.createElement('div');
        if (msg.state.author == window.twilioChat.username) {
            cont.classList.add('right-message');
        } else {
            cont.classList.add('left-message');
        }

        msgCont = document.createElement('div');
        msgCont.classList.add('message');

        msgAuthor = document.createElement('p');
        msgAuthor.classList.add('username');
        msgAuthor.innerText = msg.state.author;

        timestamp = document.createElement('p');
        timestamp.classList.add('timestamp');
        timestamp.innerText = msg.state.timestamp;

        msgCont.appendChild(msgAuthor);
        msgCont.innerText += msg.state.body;

        cont.appendChild(msgCont);
        cont.appendChild(timestamp);

        chatArea.appendChild(cont);
    }

    chatArea.scrollTop = chatArea.scrollHeight;
}

The function addMessagesToChatArea adds messages of the current conversation to the chat area when it is selected from the side nav. It is also called when new messages are added to the current conversation. A loading message is usually displayed as the messages are being fetched. Before the conversation messages are added, this loading message is removed. Messages from the current user are aligned to the right, while all other messages from group participants are aligned to the left.

This is what the loading message looks like:

Add the sendMessage function to send messages:

function sendMessage() {
    let submitBtn = document.getElementById('submitMessage');
    submitBtn.disabled = true;

    let messageForm = document.getElementById('message-input');
    let messageData = new FormData(messageForm);

    const msg = messageData.get('chat-message');

    window.twilioChat.selectedConversation.sendMessage(msg)
        .then(() => {
            document.getElementById('chat-message').value = '';
            submitBtn.disabled = false;
        })
        .catch(() => {
            showError('sending your message');
            submitBtn.disabled = false;
        });
};

When the user sends a message, the sendMessage function is called. It gets the message text from the text area and disables the submit button. Then using the currently selected conversation, the message is sent using its sendMessage method. If successful, the text area is cleared and the submit button is re-enabled. If unsuccessful, an error message is displayed instead.

The showError method displays an error message when it is called; hideError hides it.

function showError(msg) {
    document.getElementById('error-message').style.display = 'flex';
    document.getElementById('error-text').innerText = There was a problem ${msg ? msg : 'fulfilling your request'}.;
}

function hideError() {
    document.getElementById('error-message').style.display = 'none';
}

This is what this error message will look like:

The logout function logouts out the current user. It does this by making a request to the backend which clears their session. The user is then redirected to the conversation page, so they can create a new conversation if they’d like.

function logout(logoutButton) {
    logoutButton.disabled = true;
    logoutButton.style.cursor = 'wait';

    axios.request({
        url: '/auth/token',
        baseURL: 'http://localhost:8000',
        method: 'DELETEdelete',
        withCredentials: true
    })
        .then(() => {
            location.href = '/pages/conversation.html';
        })
        .catch(() => {
            location.href = '/pages/error.html';
        });
}

Add the inviteFriend function to send conversation invites:

async function inviteFriend() {
    try {
        const link = http://localhost:3000/pages/login.html?sid=${window.twilioChat.selectedConvSid};

        await navigator.clipboard.writeText(link);

        alert(The link below has been copied to your clipboard.\n\n${link}\n\nYou can invite a friend to chat by sending it to them.);
    } catch {
        showError('preparing your chat invite');
    }
}

To invite other people to participate in the conversation, the current user can send another person a link. This link is to the login page and contains the current conversation SID as a query parameter. When they click the invite button, the link is added to their clipboard. An alert is then displayed giving invite instructions.

Here is a screenshot of the invite alert:

The Login Page

On this page, a user logs in when they are invited to a conversation. You can find the markup for pages/login.html at this link.

In scripts/login.js, the login function is responsible for logging in conversation invitees. Copy its code below and add it to the aforementioned file:

function login() {
    const convParams = new URLSearchParams(window.location.search);
    const conv = Object.fromEntries(convParams.entries());

    if (conv.sid) {
        let submitBtn = document.getElementById('login-button');
        submitBtn.innerText = 'Logging in...';
        submitBtn.disabled = true;
        submitBtn.style.cursor = 'wait';

        let loginForm = document.getElementById('loginForm');
        let formData = new FormData(loginForm);
        let body = Object.fromEntries(formData.entries());

        axios.request({
            url: /api/conversations/${conv.sid}/participants,
            baseURL: 'http://localhost:8000',
            method: 'POSTpost',
            withCredentials: true,
            data: body
        })
            .then(() => {
                location.href = '/pages/chat.html';
            })
            .catch(() => {
                location.href = '/pages/error.html';
            });
    } else {
        location.href = '/pages/conversation.html';
    }
}

The login function takes the conversation sid query parameter from the URL and the username from the form. It then makes a POST request to api/conversations/{sid}/participants/ on the backend app. The backend app adds the user to the conversation and generates an access token for messaging. If successful, a session is started in the backend for the user.

The user is then redirected to the chat page, but if the request returns an error, they are redirected to the error page. If there is no conversation sid query parameter in the URL, the user is redirected to the conversation page.

Below is a screenshot of the login page:

Running the App

Before you can start the front-end app, make sure that the backend app is running. As mentioned earlier, you can start the backend app using this command on the terminal:

NODE_ENV=development npm start

To serve the front-end app, run this command in a different terminal window:

http-server -p 3000

This serves the app at http://localhost:3000. Once it’s running, head on over to http://localhost:3000/pages/conversation.html; set a name for your conversation and add your username, then create it. When you get to the chat page, click on the conversation, then click the Invite button.

In a separate incognito window, paste the invite link and put a different username. Once you’re on the chat page in the incognito window, you can begin chatting with yourself. You can send messages back and forth between the user in the first window and the second user in the incognito window in the same conversation.

Conclusion

In this tutorial, you learned how to create a chat app using Twilio Conversations and Vanilla JS. You created a Node.js app that generates user access tokens, maintains a session for them, creates conversations, and adds users to them as participants. You also created a front-end app using HTML, CSS, and Vanilla JS. This app should allow users to create conversations, send messages, and invite other people to chat. It should get access tokens from the backend app and use them to perform these functions. I hope this tutorial gave you a better understanding of how Twilio Conversations works and how to use it for chat messaging.

To find out more about Twilio Conversations and what else you could do with it, check out its documentation linked here. You can also find the source code for the backend app on Github here, and the code for the front-end app here.

How To Build Web Service Using Spring Boot 2.x

Architecture Contains

  • MVC Architecture
  • JWT Based Authentication
  • Spring Data (JPA)
  • Application User Password Encryption
  • DB password Encryption.
  • SQL Server
  • Slf4j
  • Swagger For API Doc

Repository Contains

  • Application Source code
  • SQL script of Data Base along with key data
  • DB.txt file contains the DB config details.
  • Postman JSON script to test all web services.

Steps to Run Applications

  • Install JDK 11 or the latest version.
  • Clone the Project repository into local.
  • Git Url: https://github.com/VishnuViswam/sample-web-service.git
  • Install SQL server 2012.
  • Create application DB and user
  • Insert the DB key data.
  • Add the decoding key of the database password into the system variables. It is present in the DB.txt file.
  • Sometimes we may need to restart the windows to pick up the updated system variables.
  • Run the project source code.
  • To call the web services, import provided postman JSON scripts into the postman client application.

About Project Configurations

Web-Service Declaration

Each Web-services of the application will be declared in the controller layer.

Example

Java
 
@RequestMapping("/api/v1/user")
@RestController
@Validated
public class UserController {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Autowired
    private GeneralServices generalServices;

    @Autowired
    private UserService userService;

    /**
     * Web service to create new user
     *
     * @param httpServletRequest
     * @param user
     * @return
     */
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> createUser(HttpServletRequest httpServletRequest,
                                             @Valid @RequestBody UserCreateModel user) {
        logger.debug("<--- Service to save new user request : received --->");
        ApiSuccessResponse apiResponse = userService.createUser(user, generalServices.getApiRequestedUserId(httpServletRequest));
        logger.debug("<--- Service to save new user response : given --->");
        return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);

    }

}

  • @RequestMapping("/api/v1/user") annotation is used to mention the category of web service.
  • @RestController annotation will configure the class to receive the rest-full web service call.
  • @PostMapping() annotation will decide the HTTP request type.
  • consumes & consumes tags will decide the content type of the HTTP request and response.

From this "controller layer," API requests will be taken to the service layer. All business logic will be handled here, then it will talk with the database using JPA.

Python with Mysql: Fetch the available resource from DB parallely

I have my resoruces stored in mysql DB:

eg :

|IPaddress | Status|
|10.x.x.x| yes|
|10.1.x.x| yes|
I am trying to get the available resource and after that will update DB with status No.

query = ("SELECT IPaddress from TABLE where status='yes'")
cursor = mydb.cursor()
cursor.execute(query)
result=cursor.fetchall()
query1 = ("update TABLE set Status = 'no' where IPaddress ='{}' ").format(result[0])
cursor.execute(query1)
mydb.commit()
But, my API will get called from multiple places simultaneously how i can ensure, the resource wont get allocate to different calls..

Building Server in Julia and Connecting to a Database

Introduction

With the growing popularity of the internet, building a web server has been an absolutely required skillset for any developer. However, there are popular frameworks in some languages available (like Node.js or Django) to build the backend. Still, Julia, being a relatively new language, also provides a framework for the same purpose. Julia is still under active development phase and has closer compiler performance to C than other languages.  Developers who have already implemented a machine learning or some data processing algorithm in Julia can now use the Julia Genie framework to expose APIs as well, which would help them use the same stack for the whole backend.

Installing Julia

If Julia is already installed in the system, typing $julia --version in the terminal should give version of Julia installed locally. Otherwise, official Julia documentation can be referred for installation. If installed properly, typing $julia in the terminal should start Julia in REPL mode.

Using Pluggable HumanTask Assignment Strategy in jBPM 7

jBPM 7 release provides logic to find the best suitable user to assign the task. Multiple assignment strategies are supported in this release,

  • Potential owner busyness strategy (default)
  • Business Rules Strategy
  • RoundRobin Strategy

1. Potential Owner Busyness Strategy:

This strategy makes sure that the least loaded user from the potential owner list will be selected as task owner. To make this strategy work effectively we need to provide groups of the users.It can be done by using UserInfo configured in the environment. To load user group details we can use org.jbpm.ht.userinfo option while server startup. We can load user and group mapping from DB or from LDAP. We can load user group details from the file as well.We have to create a file with a name jbpm.user.info.properties in the application classpath.

How We Achieved MongoDB Replication on Docker

Prologue

Picture your database server. Now imagine it somehow breaks. Despair comes up and disturbs the reaction.

Maybe you lost data. Maybe you had too much downtime. Maybe you lost work hours. Maybe you lost precious time and money. High Availability is easily a nice-to-have but in times like these, you value it more.

How To Add AutoComplete Textbox In React Application

Introduction

In this article, we are going to learn how we add the AutoComplete textbox in ReactJS. We use the Material UI Autocomplete component in this demo.

Prerequisites

  • We should have the basic knowledge of React.js and Web API.
  • Visual Studio and Visual Studio Code IDE should be installed on your system.
  • SQL Server Management Studio.
  • Material UI Installed.

You may also like: An Angular Autocomplete From UI to DB

Using CSCS Scripting Language For Cross-Platform Development

Using CSCS Scripting Language For Cross-Platform Development

Using CSCS Scripting Language For Cross-Platform Development

Vassili Kaplan
Our goal is not to build a platform; it’s to be cross all of them.

— Mark Zuckerberg

CSCS (Customized Scripting in C#) is an open-source scripting language implemented in C#. Syntactically it’s very similar to JavaScript, but it also has some similarities with Python. Some of these similarities are the keywords in the well-known if…elif…else construct, and also have the same variable scope definition as in Python (e.g. a variable defined inside of an if block or inside a loop will be also visible outside).

As opposed to JavaScript and Python, variables and functions in CSCS are case-insensitive. The primary goal of CSCS is to let the developer write as little code as possible. Also, the same code is used for both iOS and Android development. Additionally, CSCS can be used for Windows, Mac, and Unity development.

Note: You can read more about how Microsoft uses CSCS in their Maquette product (based on Unity) over here.

CSCS can be added to your project by embedding its C# source code into a Visual Studio Xamarin project. Unlike most other languages, you have full ownership of the CSCS source code and can easily add or modify its functionality. I’ll be sharing an example of this later on in the article.

Also, we are going to learn how to get started with CSCS and use some more advanced features that have been covered in other articles. Among these features, we are going to access a Web Service via Web Requests with JSON string parsing, and we’ll also be using SQLite on iOS and Android.

The easiest way to get started is to download a sample of a project using CSCS and start playing with the start.cscs file. This is what we’ll be doing in the next section: creating an iOS/Android app with basic GUI and events.

"Hello, World!" In CSCS

Let’s start with a relatively simple example of CSCS code that constructs a screen with a few widgets:

AutoScale();
SetBackgroundColor("light_green");

locLabelText = GetLocation("ROOT", "CENTER", "ROOT", "TOP");
AddLabel(locLabelText, "labelText", "Welcome " +
    _DEVICE_INFO_ + " " + _VERSION_INFO_ + " User!", 600, 100);

locTextEdit = GetLocation("ROOT", "LEFT", labelText,
  "BOTTOM");
AddTextEdit(locTextEdit, "textEdit", "Your name", 320, 80);

locButton = GetLocation(textEdit,"RIGHT",textEdit, "CENTER");
AddButton(locButton, "buttonHi", "Hello", 160, 80);

function buttonHi_click(sender, arg) {
  name = getText(textEdit);
  msg = name != "" ? "Hello, "+ name + "!" : "Hello, World!";
  AlertDialog("My Great App", msg);
}

The image below shows the resulting user interface on an iPhone as well as an Android device after clicking on the “Hello” button and not typing anything in the “Text Edit” field:

Hello, World!” on iPhone (left) and Android (right)
“Hello, World!” on iPhone (left) and Android (right) (Large preview)

Let’s briefly go over the code above. It starts with the AutoScale() function call, and what that does is to tell the parser that the widget sizes are relative to the screen size, i.e. they will be auto-resized (the widget will look bigger on bigger screens and smaller on smaller screens). This setting could be also overridden per widget.

Note that there is no need to create a special handler on a button click. If you define a function with name widgetName_click(), it will be used as a handler when the user clicks on a widget called widgetName (it doesn’t have to be a button, it can actually be any widget). That’s why the function buttonHi_click() will be triggered as soon as the user clicks on the button.

You may have noticed that the GUI is constructed completely in code. This is done by supplying a relative widget location when adding it. The general format of a location command is the following:

location = GetLocation(WidgetX, HorizontalPlacement, WidgetY, VerticalPlacement,
                       deltaX=0, deltaY=0, autoResize=true);

So, you can place a widget relative to other widgets on the screen. A special case of a widget is a “ROOT” widget, meaning the main screen.

After creating a location, you need to provide it as an argument to any of the following functions:

  • AddLabel,
  • AddButton,
  • AddCombobox,
  • AddStepper,
  • AddListView,
  • AddTextView,
  • AddStepper,
  • AddImageView,
  • AddSlider,
  • AddPickerView,
  • and so on.

All of the above have the same structure:

AddButton(location, newWidgetname, initialValue, width, height);

The widget width and height will be relative to the screen size if the AutoScale() CSCS command was previously run. Also, the initial value (in case of a button) is the text shown on it. This can be changed anytime by invoking SetText(widgetName, newText).

Using Visual Studio Code To Debug CSCS

We can also use Visual Studio Code to debug CSCS scripts. If you want to develop apps for both Android and iOS, you need to use a Mac. After installing Visual Studio Code, install the CSCS Debugger and REPL extension.

In order to use the extension, add this line of code anywhere in your start.cscs CSCS script:

StartDebugger();

The following image below shows how you can use Visual Studio Code to debug and change the functionality of the “Hello, World!” app that we developed in the previous section. In the upcoming example, we’ll be adding a label and a button on the fly to the existing layout.

To do this, we just select the code to be executed by the parser and press Ctrl + 8. As a result, a label and a button will be added at the center of the screen. We also add a button handler that will update the new label with the current time on each button click.

Changing Layout on the fly with Visual Studio Code
Changing Layout on the fly with Visual Studio Code (Large preview)

Using SQLite In CSCS

SQLite is an ACID (Atomicity, Consistency, Isolation, Durability) type of a relational database, and was developed by Richard Hipp (the first version was released in 2000). In difference to other relational databases, like Microsoft SQL Server or Oracle Database, it’s embedded. (Embedded not only into the device, but also into the end program.) It’s included in the program as a very compact library, which is less than 500 KB in size. But two apps (released by the same developer) can read the same SQLite DB if the DB file path is known to both apps.

The advantage of SQLite is that it can be used without an extra installation on an iOS or an Android device. The disadvantage is that it obviously cannot hold as much data as a “normal” DB and also that it’s weakly typed (i.e. you can insert a string instead of an integer — it will then be converted to an integer or 0 on failure). On the other hand, the latter can be also seen as an advantage as well.

SQLite can be easily used from CSCS without extra import statements. Here’s a table that will help you get an overview of the main SQLite functions used in CSCS:

Command Description
SQLInit(DBName) Initializes a database or sets a database to be used with consequent DB statements.
SQLDBExists(DBName) Checks whether the DB has been initialized. Also sets the database to be used with consequent DB statements.
SQLQuery(query) Executes an SQL query (a select statement). Returns a table with records.
SQLNonQuery(nonQuery) Executes an SQL non-query, e.g. an update, create or delete statement. Returns number of records affected.
SQLInsert(tableName, columnList, data) Inserts passed table of data of records to the specified DB table. The columnList argument has the following structure: colName1,colName2,…,colNameN

Table 1: SQLite commands in CSCS

This is how the SQLInit() and SQLDBExists() functions are typically used:

DBName = "myDB.db1";

if (!SQLDBExists(DBName)) {
  create = "CREATE TABLE [Data] (Symbol ntext, Low real,
    High real, Close real, Volume real,
    Stamp text DEFAULT CURRENT_TIMESTAMP)";
  SQLNonQuery(create);
}


SQLInit(DBName);

We are going to see more examples of how you can select and insert data into an SQLite database later on. I’ll show you an example of how to write stock data that has been extracted from a Web Service into a local SQLite database.

Adding Custom Functionality To CSCS

In this section, we are going to see how you can extend the CSCS functionality. As an example, we are going to see the existing implementation of the CSCS Sleep function below.

To add custom functionality, all you need to do is create a new class by deriving from the ParserFunction class, overriding its Evaluate() method, and registering this class with the parser. Here’s a short version (without error checking):

class SleepFunction : ParserFunction
{
  protected override Variable Evaluate(ParsingScript script)
  {
    List  args = script.GetFunctionArgs();
    int sleepms = Utils.GetSafeInt(args, 0);
    Thread.Sleep(sleepms);

    return Variable.EmptyInstance;
  }
}

Registration of a class with the parser can be done anywhere in the initialization stage via the following command:

ParserFunction.RegisterFunction("Sleep", new SleepFunction());

That’s it! Now the Evaluate() method of the SleepFunction class will be invoked as soon as a “Sleep” token is extracted by the parser.

Note that CSCS is case insensitive (except the core control flow statements: if, elif, else, for, while, function, include, new, class, return, try, throw, catch, break, continue). This means that you can type either “sleep(100)” or “Sleep(100)” — both calls will suspend the executing thread for 100 milliseconds.

Processing JSON In CSCS

JSON (JavaScript Object Notation) is a lightweight data interchange format, consisting of attribute-value pairs and array-type pairs. It was developed by Douglas Crockford in the early 2000s (around same time when SQLite appeared as well).

In this section, we are going to learn how to parse JSON using CSCS.

The CSCS function to parse a JSON string is GetVariableFromJSON(jsonText). This function returns a hash table in which the keys are the attributes from the JSON string.

Consider the following example of a JSON string:

jsonString = '{ "eins" : 1, "zwei" : "zweiString", "mehr" : { "uno": "dos" },
               "arrayValue" : [ "une", "deux" ] }';

After invoking:

a = GetVariableFromJSON();

The variable a will be a hash table with the following values:

a["eins"] = 1
a["zwei"] = "zweiString"
a["mehr"]["uno"] = "dos"
a["arrayValue"][0] = "une"
a["arrayValue"][1] = "deux"

In the next section, we are going to see another example of parsing a JSON string from a Web Service.

An Example Of An App With SQLite, Web Requests And JSON

For an app using SQLite, a Web Service and JSON parsing, we are going to use Alpha Vantage Web Service. You can get an API Key for free but the free version allows accessing their web service no more than 5 times per minute.

Using Alpha Vantage, you can extract various financial data sets — including stock prices. This is what we are going to do in our sample app.

The image below shows how the Stocks apps looks on an iOS and on an Android device.

Extracting Stocks from Alpha Vantage Web Service on iOS (left) and Android (right)
Extracting Stocks from Alpha Vantage Web Service on iOS (left) and Android (right) (Large preview)

The CSCS code to build the GUI is the following:

locLabel = GetLocation("ROOT","CENTER", "ROOT","TOP", 0,30);
AddLabel(locLabel, "labelRefresh", "", 480, 60);

locSFWidget = GetLocation("ROOT","CENTER",
                          labelRefresh,"BOTTOM");
AddSfDataGrid(locSFWidget,  "DataGrid", "",
              graphWidth, graphHeight);

listCols = {"Symbol","string",  "Low","number", "High",
            "number", "Close","number",  "Volume","number"};
AddWidgetData(DataGrid, listCols, "columns");
colWidth = {17, 19, 19, 19, 26};
AddWidgetData(DataGrid, colWidth, "columnWidth");

locButton = GetLocation("ROOT","CENTER",DataGrid,"BOTTOM");
AddButton(locButton, "buttonRefresh", "Refresh", 160, 80);

locLabelError = GetLocation("ROOT","CENTER","ROOT","BOTTOM");
AddLabel(locLabelError, "labelError", "", 600, 160);
SetFontColor(labelError, "red");
AlignText(labelError, "center");

getDataFromDB();

The getDataFromDB() method will extract all the data from the SQLite database. It uses the SQL query defined as follows:

query = "SELECT Symbol, Low, High, Close, Volume, DATETIME(Stamp,
               'localtime') as Stamp FROM Data ORDER BY Stamp DESC LIMIT 5;";

Take a look at the code below for the getDataFromDB() implementation.

function getDataFromDB() {
  results = SQLQuery(query);
  for (i = 1; i < results.Size; i++) {
    vals       = results[i];
    stock      = vals[0];
    low        = Round(vals[1], 2);
    high       = Round(vals[2], 2);
    close      = Round(vals[3], 2);
    volume     = Round(vals[4], 2);
    refresh    = vals[5];

    stockData  = {stock, low, high, close, volume};
    AddWidgetData(DataGrid, stockData, "item");
  }
  SetText(labelRefresh, "DB Last Refresh: " + refresh);
  lockGui(false);
}

Now let’s see how we get data from the Alpha Vantage Web Service. First, we initialize the data:

baseURL     = "https://www.alphavantage.co/query? " +
              "function=TIME_SERIES_DAILY&symbol=";
apikey      = "Y12T0TY5EUS6BC5F";
stocks      = {"MSFT", "AAPL", "GOOG", "FB", "AMZN"};
totalStocks = stocks.Size;

Next, we load stocks one by one as soon as the user clicks on the “Refresh” button:

function buttonRefresh_click(object, arg) {
  lockGui();

  SetText(labelRefresh, "Loading ...");
  SetText(labelError, "");
  ClearWidget(DataGrid);
  loadedStocks = 0;
  getData(stocks[loadedStocks]);
}

function getData(symbol) {
  stockUrl  = baseURL + symbol + "&apikey=" + apikey;
  WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure");
}

Here’s the main CSCS function to use in order to get data from a Web Service:

WebRequest("GET", stockUrl, "", symbol, "OnSuccess", "OnFailure");

The last two parameters are functions to invoke on completion of the web request. For example, in case of a failure, the following CSCS function will be called:

function OnFailure(object, errorCode, text)
{
  SetText(labelError, text);
  lockGui(false);
}

As a result, the user will get an error message as shown below:

An error when requesting web data
An error when requesting web data (Large preview)

But, if all is good, we are going to parse the JSON string and insert its contents into the SQLite DB.

function OnSuccess(object, errorCode, text)
{
  jsonFromText  = GetVariableFromJSON(text);
  metaData      = jsonFromText[0];
  result        = jsonFromText[1];

  symbol        = metaData["2. Symbol"];
  lastRefreshed = metaData["3. Last Refreshed"];
  allDates      = result.keys;

  dateData   = result[allDates[0]];
  high       = Round(dateData["2. high"],  2);
  low        = Round(dateData["3. low"],   2);
  close      = Round(dateData["4. close"], 2);
  volume     = dateData["5. volume"];
  stockData  = {symbol, low, high, close, volume};
  SQLInsert("Data","Symbol,Low,High,Close,Volume",stockData);

  if (++loadedStocks >= totalStocks) {
    getDataFromDB();
  } else {
    getData(stocks[loadedStocks]);
  }
}

In order to understand how we access different fields in the hash table above, let’s take a look at the actual string received from the Alpha Vantage web request:

{   "Meta Data": {
        "1. Information": "Daily Prices (open, high, low, close) and Volumes",
        "2. Symbol": "MSFT",
        "3. Last Refreshed": "2019-10-02 14:23:20",
        "4. Output Size": "Compact",
        "5. Time Zone": "US/Eastern"
    },
    "Time Series (Daily)": {
        "2019-10-02": {
            "1. open": "136.3400",
            "2. high": "136.3700",
            "3. low": "133.5799",
            "4. close": "134.4100",
            "5. volume": "11213086"
        },
   …
    }
}

As you can see, we get the latest date as the first element of the allDates array that consists all of the extracted dates.

Conclusion

Adding CSCS to your project is easy. All you need to do is simply embed the source code of CSCS as a module to your project — just like it’s done in a sample Xamarin project.

Do you use and extend CSCS scripting language in your projects? Leave a comment below — I’d be happy to hear from you!

Further Reading

If you want to explore the CSCS language a bit more, here are some of the articles I’ve written about on the topic:

As an additional resource, I also recommend reading how you can improve CSCS performance by precompiling its functions.

Smashing Editorial (ra, yk, il)

The 5 Pitfalls of Legacy Database Environments

Find out the 5 pitfalls of legacy DB environments.

Microsoft SQL Server, Oracle, SAP HANA, PostgreSQL, MySQL. For many organizations, these databases, among others, are essential components of their success, but their potential is stunted. That’s because too many of these databases are still running on legacy IT infrastructure.

Riddled with silos and complexity in every corner, legacy IT is neither efficient nor scalable enough to continue running these databases. Beyond the poor performance and inefficiency of running relational databases on legacy infrastructure, your IT team is likely struggling with one, two, or all of the following pitfalls.

Custom MySQL Docker Instance

Hi folks, have you ever been in a situation where you were required to set up or mimic the current MySQL instance of any environment? Well recently, I have been in such a situation, and my use case was to test the overall services, so I decided to use Minikube for service deployment as a whole. Now, when I had to deploy a MySQL instance, it was quite a tedious task to run a normal MySQL container and then source the DB script for the database every time a new container comes up.

Sometimes it was quite annoying because the dump takes 10-15 minutes to get populated. Another issue was that if you think that you can dump the file in a running container and the Docker commit will do the trick, it won't!

ADF Faces and Client-Side Value With innerHTML

In ADF Faces, you can leverage the full power of JavaScript. I will explain how you could assign a value from ADF Faces component to the plain HTML div.

The sample app is available on GitHub repo. It doesn't require DB connection, you can run it straight away in Oracle JDeveloper.

How to Connect PostgreSQL With Java Application

PostgreSQL is a powerful, open-source SQL database with the object-relational structure and numerous robust features to ensure excellent performance and reliability. In this tutorial, we’ll show how to connect PostgreSQL database with Java application hosted with Jelastic PaaS.

1. Log into Jelastic dashboard, create New Environment with the Java application server and the PostgreSQL database.

Branda 3.1 Smarter SMTP, Signup Codes, and +25 Upgrades

Branda is all about giving you fast easy control over white labeling WordPress…and quite frankly she rocks it. She just got some special new configurations for managing users, lightning fast setup, compatibility checks, and even more brand magic.

“How is that possible”, you ask? Branda 3.0 was unveiled just a month ago with an incredible new design and setup wizard. It was a big hit by the way. Thanks, everyone for the feedback.

But with Branda now sporting our signature hero interface we were able to quickly add +25 additions and feature upgrades.

So let’s dive in and explore the highlight features in Branda 3.1.

Not using Branda yet? Get a copy with a free 30-day WPMU DEV trial. No obligation. Cancel at any time and keep Branda as our gift.

Signup Codes With User Role Settings

Have an exclusive offer that requires site registration? Want to limit spam registrations? Looking to allow specific signups to access different user roles? Time to try Branda’s brand new Signup Code features.

Signup Codes make it so only people with a special code can register to your site.

Here’s what’s new:

  • Support for multiple codes – before you could set up just one registration code at a time. Now you can create as many unique codes as you need.
  • Give each code a user role – the ability to assign users a specific role based on the code they use when registering.

Allow a visitor to register as a subscriber without a code or create a special code that lets a user set up a Writer or Editor account. Not only do signup codes improve function, but it also helps eliminate unnecessary bot signups. Thank you again, Branda!

Branda user roles and multiple signup codes
Use Signup Codes for letting users register for different user roles.

Branda Importer Enhancements

We’re continually looking for ways to simplify configuration for people managing multiple sites, from setting up new sites to site migration. The Branda import and export tool makes it easy to move your configuration options across multiple sites.

In 3.1 that includes the logo, background and favicon image files for the Admin Bar, DB Error Page, Login Screen, Site Status Pages, and Website Mode modules. Clone your look and style settings and load up your images all in a few clicks.

Branda Import and export all your settings and images
Import the specific modules and images you want for speedy set up.

The importer also regenerates your log DB error and site status files after an import so you get all the fresh looks without sharing the information.

Simplify SMTP Email Setting

By default, WordPress uses the PHP Mail function to send emails. That is not great, especially for sites that are scaling up. With Branda, you can use SMTP to send your messages including sending from a 3rd-party mail provider.

If your system emails keep ending up in the spam folder then you definitely need to check this feature out.

Branda SMTP settings for WordPress
Branda lets you send system emails from your favorite 3rd-party services.

Again, this option is not new to Branda, but she did get smarter. In 3.1, Branda checks for other SMTP plugins on your site, notifies you of conflicts, and can even import SMTP settings from other popular tools.

Consolidate with Branda and she’ll take care of the set up.

WPMU DEV Hero Hider

Don’t really need to spend too much time here, but the the best white label plugin for WordPress got the WPMU DEV Hero Hider. This has become a favorite for our members. Basically, instead of displaying WPMU DEV branding and hero graphics you can replace them with your own… now including Branda.

Put Branda in stealth mode and remove WPMU DEV images or display your own logo where Branda sits in the dashboard. Groovy!

WPMU DEV Dashboard plugin Hero Hider
The WPMU DEV Hero Hider lets you replace all the hero branding from WPMU DEV plugins.

Get Started With Branda

Those are just the highlights. Branda 3.1 is stacked with over 25 additions, upgrades, and improvements including a bunch of cool little styling options, a custom CSS field for the author box, and the ability to hide custom menus for mobile users.

Branda is brilliant and just keeps getting better. But you don’t have to take my word for it. Start using Branda and all the WPMU DEV plugins, services and support absolutely free. Cancel anytime in the next 30-days, you won’t be charged, and we’ll let you keep Branda.