Categories
Careers

Top Mistakes Freelancers Should Avoid

Freelancing is something that may be tough for some people. If we avoid some mistakes, we can save ourselves from some grief.

In this article, we’ll look at some advice to follow as freelancers.

Forgetting that I’m a business owner

You aren’t working for anyone as an employee. Your client is a partner you’re working with.

So you shouldn’t expect clients to dictate what you do.

Trying to Sell Your Skills Instead of Solutions to Clients

Clients want solutions, so selling them solutions you can offer them is better than selling what skills you have.

Competing Mainly on Price

Good clients are willing to pay you good prices, so don’t compete on price.

You don’t want to work hard for low pay.

Viewing Other Freelancers Solely as Competition and not Building a Network

Some freelancers can be your competitors, but they can also help you find work and refer you to people they know.

Niching Down Too Much and Too Fast

The narrower the niche, the harder it is to find people that need your specialization.

Not Marketing Until Your Run Out of Projects

It’s a good idea to maintain your presence all the time so that you get noticed by potential clients.

It takes time for you and clients to find each other, so it’s better to start early so it’s less likely that you run out of work.

Not Saving Enough Money

There’re always unexpected expenses and slow months, so saving money is always important in case you need the money.

Dipping to Rock Bottom Rates

If you work for a rate that’s too low, you may be working on projects that aren’t profitable.

That means you’re wasting money.

Wasting Time on Useless Things

You should prioritize useful things for clients and making money freelancing.

Anything else can wait.

Of course, taking breaks is also important.

Relying Too Much on One Source of Work

Always diversify in case one source of income runs out.

Relying on Freelance Platforms

Freelance platforms may work for some people.

But you don’t control them and they can cut you off with a click of a button.

You don’t want them to cut you off from your clients.

Always diversify on sources of client and income.

Keeping Bad Clients for Too Long

If they’re shady or you aren’t profitable working with them, then cut them off as soon as you can.

Not Having Formal Processes

Processes speed things up and keep things consistent, so it’s worth having them.

Setting standards for quality and other workflows are helpful.

Taking in Too Much Advice

There’re lots of advice online. They’re either conflicting or may not be relevant to your situation.

They may lead you in the wrong direction, so it’s a good idea to take a break from reading advice.

Conclusion

If we follow the advice above, we can all be successful freelancers.

Categories
Hapi

Server-Side Development with Hapi.js — Caching and Cookie

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Caching

We can control caching by setting some properties in our routes.

For example, we can write:

const Hapi = require('@hapi/hapi');

const init = async () => {

  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    path: '/{ttl?}',
    method: 'GET',
    handler(request, h) {
      const response = h.response({
        hello: 'world'
      });
      if (request.params.ttl) {
        response.ttl(request.params.ttl);
      }
      return response;
    },
    options: {
      cache: {
        expiresIn: 30 * 1000,
        privacy: 'private'
      }
    }
  });

  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We call the route with server.route .

Then we call response.ttl to set the duration of the caching of the response.

We also set the options.cache.expiresIn property to set when the cache expires.

And privacy set whether the cache is private.

Last-Modified Header

We can also set the Last-Modified header added automatically to the response.

For instance, we can write:

const Hapi = require('@hapi/hapi');
const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });
  server.route({
    path: '/{ttl?}',
    method: 'GET',
    handler(request, h) {
      return h.response({ hello: 'world' })
        .header('Last-Modified', lastModified.toUTCString());
    },
    options: {
      cache: {
        expiresIn: 30 * 1000,
        privacy: 'private'
      }
    }
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call header with the Last-Modified key and the and the value

Cookies

We can add a cookie by calling the h.state method.

For instance, we can write:

const Hapi = require('@hapi/hapi');
const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    path: '/',
    method: 'GET',
    async handler (request, h) {
      h.state('data', { firstVisit: false });
      return h.response('Hello');
    },
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We call h.state with the key and value.

Also, we can write:

const Hapi = require('@hapi/hapi');
const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    path: '/',
    method: 'GET',
    async handler (request, h) {
      return h.response('Hello').state('data', { firstVisit: false });
    },
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

to do the same thing.

We can override the options by writing:

const Hapi = require('@hapi/hapi');
const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    path: '/',
    method: 'GET',
    async handler (request, h) {
      return h.response('Hello').state('data', 'test', { encoding: 'none' });
    },
  });
  await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});
init();

We set the encoding option by calling the state method with a 3rd argument.

Conclusion

We can add caching and set cookies in our web app with Hapi.

Categories
Hapi

Server-Side Development with Hapi.js — Login and Cookie Auth

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Cookie

We can add cookie authentication easily with a few lines of code.

For example, we can write:

const Bcrypt = require('bcrypt');
const Hapi = require('@hapi/hapi');

const users = [{
  username: 'john',
  password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',
  name: 'John Doe',
  id: '1'
}];

const start = async () => {

  const server = Hapi.server({
    port: 4000,
    host: '0.0.0.0'
  });

  await server.register(require('@hapi/cookie'));

  server.auth.strategy('session', 'cookie', {
    cookie: {
      name: 'sid-example',
      password: '!wsYhFA*C2U6nz=Bu^%A@^F#SF3&kSR6',
      isSecure: false
    },
    redirectTo: '/login',
    validateFunc: async (request, session) => {
      const account = await users.find(
        (user) => (user.id === session.id)
      );

      if (!account) {
        return {
          valid: false
        };
      }

      return {
        valid: true,
        credentials: account
      };
    }
  });

  server.auth.default('session');

  server.route([{
      method: 'GET',
      path: '/',
      handler (request, h) {
        return 'Welcome to the restricted home page!';
      }
    },
    {
      method: 'GET',
      path: '/login',
      handler (request, h) {
        return `<html>
            <head>
                <title>Login page</title>
            </head>
            <body>
                <h3>Please Log In</h3>
                <form method="post" action="/login">
                    Username: <input type="text" name="username"><br>
                    Password: <input type="password" name="password"><br>
                <input type="submit" value="Login"></form>
            </body>
        </html>`;
      },
      options: {
        auth: false
      }
    },
    {
      method: 'POST',
      path: '/login',
      handler: async (request, h) => {

        const {
          username,
          password
        } = request.payload;
        const account = users.find(
          (user) => user.username === username
        );

        if (!account || !(await Bcrypt.compare(password, account.password))) {
          return h.view('/login');
        }

        request.cookieAuth.set({
          id: account.id
        });

        return h.redirect('/');
      },
      options: {
        auth: {
          mode: 'try'
        }
      }
    }
  ]);

  await server.start();
  console.log('server running at: ' + server.info.uri);
};

start();

We have the users array with the user data.

password is the hash of the password. It’s 'secret' in plain text.

The start function has the code for our app.

The Hapi.server function creates the server.

We register the @hapi/cookie module with:

await server.register(require('@hapi/cookie'));

Then we add the user authentication fucntion with:

server.auth.strategy('session', 'cookie', {
  cookie: {
    name: 'sid-example',
    password: '!wsYhFA*C2U6nz=Bu^%A@^F#SF3&kSR6',
    isSecure: false
  },
  redirectTo: '/login',
  validateFunc: async (request, session) => {
    const account = await users.find(
      (user) => (user.id === session.id)
    );

    if (!account) {
      return {
        valid: false
      };
    }

    return {
      valid: true,
      credentials: account
    };
  }
});

We call the server.auth.strategy method to add authentication.

'session' is the name. 'cookie' is the strategy.

The object has the validation logic. cookie sets the cookie secret.

redirect adds the redirect when auth fails.

validateFunc has the logic to validate the credentials.

We check if the user has a valid cookie with:

const account = await users.find(
  (user) => (user.id === session.id)
);

We return an object with the valid property to indicate whether the credentials are valid.

credentials has the credentials for the account.

Then we call server.route to add the routes. The GET / route is the restricted route.

GET /login displays the login form.

We set options.auth to false to let us access the page without authentication.

The POST /login route lets us search for the user with the given username and password.

We validate the password with Bcrypt.compare .

And then we call h.redirect to the route we want to access if the username and password are valid.

Otherwise, we redirect to the login route.

Conclusion

We can create an app with a login page and cookie authentication easily with Hapi.

Categories
Hapi

Getting Started with Server-Side Development with Hapi.js

Hapi.js is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Hapi.js.

Getting Started

We can start by creating a project folder, go into it, and run:

npm install @hapi/hapi

to install the package.

Creating a Server

Once we installed the package, we can create a simple back end app by writing:

const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({
    port: 3000,
    host: '0.0.0.0'
  });

  server.route({
    method: 'GET',
    path: '/',
    handler: (request, h) => {
      return 'Hello World!';
    }
  });

await server.start();
  console.log('Server running on %s', server.info.uri);
};

process.on('unhandledRejection', (err) => {
  console.log(err);
  process.exit(1);
});

init();

We require the hapi package.

Then we run the Hapi.server method to create the server.

host is set to '0.0.0.0' to listen for request from all IP addresses.

And then to create a route, we call the server.route method.

The method is the request method.

handler is the request handler function.

Then we call server.start to start the server.

Next, we add the unhandledRejection error handler to exit the app gracefully when the app crashes.

Authentication

We can add authentication to our app with a few lines of code.

For instance, we can write:

const Bcrypt = require('bcrypt');
const Hapi = require('@hapi/hapi');

const users = {
  john: {
    username: 'john',
    password: '$2a$10$iqJSHD.BGr0E2IxQwYgJmeP3NvhPrXAeLSaGCj6IR/XU5QtjVu5Tm',
    name: 'John Doe',
    id: '1'
  }
};

const validate = async (request, username, password) => {
  const user = users[username];
  if (!user) {
      return { credentials: null, isValid: false };
  }

const isValid = await Bcrypt.compare(password, user.password);
  const credentials = { id: user.id, name: user.name };
  return { isValid, credentials };
};

const start = async () => {
  const server = Hapi.server({ port: 4000 });
  await server.register(require('@hapi/basic'));
  server.auth.strategy('simple', 'basic', { validate });
  server.route({
    method: 'GET',
    path: '/',
    options: {
        auth: 'simple'
    },
    handler(request, h) {
      return 'welcome';
    }
  });
  await server.start();
  console.log('server running at: ' + server.info.uri);
};

start();

to add simple authentication with the bcrypt and the hapi-basic modules.

The users object has the user data.

The validate function gets the request data from the request parameter.

We get the user by the username .

Then we call Bcrypt.compare to compare the password we entered with the hash we stored in the users object’s password property.

Then we have:

server.auth.strategy('simple', 'basic', { validate });

to add the basic authentication strategy.

And we define our route with the server.route .

The options.auth property is set to the name of the authentication strategy we added.

Now when we go to the / route, we see the login dialog box.

And when we enter john for username and secret for password, we see the welcome response.

Conclusion

We can create a simple app with authentication with Hapi.

Categories
Fastify

Server-Side Development with Fastify — Errors and Body Parser

Fastify is a small Node framework for developing back end web apps.

In this article, we’ll look at how to create back end apps with Fastify.

Errors

We can throw various kinds of errors in our Fastify app.

For example, we can write:

const fastify = require('fastify')({})

fastify.get('/' ,async () => {
  throw new Error('error')
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Then when we go to / , we get a 500 response.

Fastify includes the following errors codes:

  • FST_ERR_BAD_URL — The router received an invalid URL.
  • FST_ERR_CTP_ALREADY_PRESENT — The parser for this content type was already registered.
  • FST_ERR_CTP_INVALID_TYPE — The Content-Type should be a string.
  • FST_ERR_CTP_EMPTY_TYPE — The content type cannot be an empty string.
  • FST_ERR_CTP_INVALID_HANDLER — An invalid handler was passed for the content type.
  • FST_ERR_CTP_INVALID_PARSE_TYPE — The provided parse type is not supported. Accepted values are string or buffer.
  • FST_ERR_CTP_BODY_TOO_LARGE — The request body is larger than the provided limit.
  • FST_ERR_CTP_INVALID_MEDIA_TYPE — The received media type is not supported (i.e. there is no suitable Content-Type parser for it).
  • FST_ERR_CTP_INVALID_CONTENT_LENGTH — Request body size did not match Content-Length.
  • FST_ERR_DEC_ALREADY_PRESENT — A decorator with the same name is already registered.
  • FST_ERR_DEC_MISSING_DEPENDENCY — The decorator cannot be registered due to a missing dependency.
  • FST_ERR_HOOK_INVALID_TYPE — The hook name must be a string.
  • FST_ERR_HOOK_INVALID_HANDLER — The hook callback must be a function.
  • FST_ERR_LOG_INVALID_DESTINATION — The logger accepts either a ‘stream’ or a ‘file’ as the destination.
  • FST_ERR_REP_ALREADY_SENT — A response was already sent.
  • FST_ERR_SEND_INSIDE_ONERR — You cannot use send inside the onError hook.
  • FST_ERR_REP_INVALID_PAYLOAD_TYPE — Reply payload can either be a string or a Buffer.
  • FST_ERR_SCH_MISSING_ID — The schema provided does not have $id property.
  • FST_ERR_SCH_ALREADY_PRESENT — A schema with the same $id already exists.
  • FST_ERR_SCH_VALIDATION_BUILD — The JSON schema provided for validation to a route is not valid.
  • FST_ERR_SCH_SERIALIZATION_BUILD — The JSON schema provided for serialization of a route response is not valid.
  • FST_ERR_PROMISE_NOT_FULLFILLED — A promise may not be fulfilled with undefined when statusCode is not 204.
  • FST_ERR_SEND_UNDEFINED_ERR — Undefined error has occurred.

Content-Type Parser

We can add parsers for different content types.

For instance, we can write:

const fastify = require('fastify')({})

fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
  try {
    const json = JSON.parse(body)
    done(null, json)
  } catch (err) {
    err.statusCode = 400
    done(err, undefined)
  }
})

fastify.post('/', async () => {
  return 'success'
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to add our own JSON request body parser.

We call fastify.addContentTypeParser with the 'application/json' argument to set the JSON.

Then we parse the JSON with JSON.parse .

We can also add a catch-all body parser with the '*' argument.

For example, we can write:

const fastify = require('fastify')({})

fastify.addContentTypeParser('application/json', { parseAs: 'string' }, function (req, body, done) {
  try {
    const json = JSON.parse(body)
    done(null, json)
  } catch (err) {
    err.statusCode = 400
    done(err, undefined)
  }
})

fastify.addContentTypeParser('*', function (request, payload, done) {
  let data = ''
  payload.on('data', chunk => { data += chunk })
  payload.on('end', () => {
    done(null, data)
  })
})

fastify.post('/', async () => {
  return 'success'
})

const start = async () => {
  try {
    await fastify.listen(3000, '0.0.0.0')
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

to get the data chunks and concatenate it to the data .

Conclusion

We can throw errors in route handlers and add our own body parser into our own Fastify app.