ખીલો ખોદતા ખાણ જો મળે ઝવેરાતની, તો નિરાંતે તારી ભક્તિ કરું શામળા | ભલું થયું ભાંગી જંજાળ, નવી નક્કોર દેશે શ્રીગોપાળ | જે તુજથી ના થઈ શકે એ જ કરાવ પ્રભુ પાસે, પાણિયારું પ્રભુ ભરશે નહિ ભરશે નદી તળાવ

Kiran Chauhan

Cogito, ergo sum // I think, therefore I am.

મંત્રયોગ

—

યોગની સાધના કરવા માટેના જે ઉપાયો, તેને અંગ કહેવામાં આવે છે. તેની સંખ્યા સંપ્રદાય પરત્વે જુદી જુદી છે. મહર્ષિ પતંજલિએ યોગશાસ્ત્રમાં આઠ અંગોનું વર્ણન કરેલું છે, જ્યારે મંત્રયોગના પુરસ્કર્તાઓએ તેનાં સોળ અંગો માનેલાં છે. તે અંગે તેમણે જણાવ્યું છે કે—

भवन्ति मन्त्रयोगस्य षोडशाङ्गानि निश्चितम् ।
यथा सुधांशोर्जायन्ते कलाः षोडश शोभनाः ॥
भक्तिः शुद्धिश्चाऽऽसनञ्च पञ्चाङ्गस्याऽपि सेवनम् ।
आचारधारणे दिव्यदेशसेवनमित्यपि ॥
प्राणक्रिया तथा मुद्रा तर्पणं हवनं बलिः ।
यागो जपस्तथा ध्यानं समाधिश्चेति षोडश ॥

~ મંત્રયોગ સંહિતા

"જેમ ચંદ્રમાને સુંદર સોળ કળાઓ હોય છે, તેમ મંત્રયોગને પણ સોળ કળાઓ છે. તે આ પ્રમાણે : (૧) ભક્તિ, (૨) શુદ્ધિ, (૩) આસન, (૪) પંચાંગસેવન, (૫) આચાર, (૬) ધારણા, (૭) દિવ્યદેશસેવન, (૮) પ્રાણક્રિયા, (૯) મુદ્રા, (૧૦) તર્પણ, (૧૧) હવન, (૧૨) બલિ, (૧૩) યાગ, (૧૪) જપ, (૧૫) ધ્યાન અને (૧૬) સમાધિ."

૧. ભક્તિ: ઇષ્ટદેવ અને ગુરુ પ્રત્યે પરમ શ્રદ્ધા અને સમર્પણ ભાવ.
૨. શુદ્ધિ: શરીર, મન, સ્થાન અને પૂજા સામગ્રીની પવિત્રતા.
૩. આસન: મંત્ર જાપ વખતે બેસવા માટેની સ્થિર અને સુખદાયક સ્થિતિ.
૪. પંચાંગ સેવન: મંત્રના પાંચ અંગો (પટલ, પદ્ધતિ, કવચ, સહસ્ત્રનામ અને સ્તોત્ર) નો પાઠ કરવો.
૫. આચાર: સદાચાર, નિયમિતતા અને સાત્વિક જીવનશૈલીનું પાલન.
૬. ધારણા: ઇષ્ટદેવના સ્વરૂપ કે મંત્રના અક્ષરો પર મનને એકાગ્ર કરવું.
૭. દિવ્યદેશ સેવન: પવિત્ર અને એકાંત સ્થળો (જેમ કે નદી કિનારો, ગુફા કે મંદિર) માં સાધના કરવી.
૮. પ્રાણક્રિયા: શ્વાસોચ્છ્વાસ પર નિયંત્રણ મેળવવું (પ્રાણાયામ).
૯. મુદ્રા: સાધના દરમિયાન હાથ કે શરીરની વિશેષ આકૃતિઓ કે અંગવિન્યાસ ધારણ કરવા.
૧૦. તર્પણ: મંત્રના દેવતા અને ઋષિઓને જળ અર્પણ કરવું.
૧૧. હવન: મંત્ર બોલીને અગ્નિમાં આહુતિ આપવી (યજ્ઞ કરવો).
૧૨. બલિ: બલિ એટલે દેવતા પ્રત્યેનું સમર્પણ. તે બાહ્યભાવે ફળ-ફૂલ તથા નૈવેદ્યની સામગ્રી અર્પણ કરવાથી થાય છે અને અભ્યંતરભાવે તેમના પ્રત્યે પરમ શ્રદ્ધા રાખી સમર્પિત થઈ જવાથી થાય છે.
૧૩. યાગ: ઇષ્ટદેવની માનસિક કે બાહ્ય વિધિપૂર્વક પૂજા-અર્ચના કરવી.
૧૪. જપ: મંત્રનું વારંવાર એકાગ્રતાપૂર્વક રટણ કે આવર્તન કરવું.
૧૫. ધ્યાન: ઇષ્ટદેવના દિવ્ય સ્વરૂપનું સતત ચિંતન કરવું.
૧૬. સમાધિ: સાધનાની સર્વોચ્ચ સ્થિતિ, જેમાં મંત્ર, દેવતા અને સાધક ત્રણેય એકરૂપ થઈ જાય છે.

પરંતુ આજે આ સોળ અંગોનું આલંબન લઈને મંત્રયોગ સિદ્ધ કરનારા કેટલા? એ વિચારણીય છે. આપણને લાગે છે કે આ ક્રિયાઓ કંઈક લાંબી અને કઠિન જણાતાં તાંત્રિક યુગમાં તેમાંની સારભૂત ક્રિયાઓ લઈને મંત્ર-સાધનાની પદ્ધતિને સરળ બનાવવામાં આવી છે.


Extra Functionalities

—

In the previous chapters, we created a Sails application and customized it to our needs. We also saw how we can change some of the default configurations provided by Sails.

Before we move forward, I’d like to discuss some of the functionalities that Sails provides out of the box. Personally, I think some of these functionalities are extra. By extra, I mean that they are useful, but they don’t necessarily need to be part of the framework.

This chapter is part of my book, Understanding and Using Sails.js, which I'm writing and publishing chapter by chapter.

You can read the first few chapters for free on this blog. Visit the book's dedicated page for more information, or head over to Gumroad to purchase a copy and/or support the ongoing writing of this book.

I believe a framework is nothing more than reusable code and some common conventions that its creators decide on our behalf. The idea is to save us from writing the same code again and again. But does every useful piece of code need to be part of the framework?

Let’s take a look at some of the functionalities Sails provides and see what happens when we don’t need them.

---

Blueprint is one of the interesting features of Sails. It automatically provides routes and actions for our models, so we can perform common CRUD operations without writing the code ourselves.

Let’s understand this with an example. Go ahead and run the application first. We have the Contact model. So, in your browser (or GUI API client), visit the following URL.

http://localhost:3000/contact/create?firstName=Kai&lastName=Doe

You should see a response like this:

{
    "id": 1,
    "firstName": "Kai",
    "lastName": "Doe"
}

This is what I meant when I said that Blueprint automatically provides routes and actions for our models, allowing us to perform common CRUD operations without writing the code ourselves. If you now visit the home page, you will notice that we’re already seeing the result of Contact.find() in the console.

For a given model, Sails provides Blueprint actions such as find, findOne, create, update, replace, destroy, populate, add, and remove without requiring us to write them ourselves. For example, to delete the contact we just created, visit the following URL in your browser.

http://localhost:3000/contact/destroy/1

It will return the deleted contact. If you visit the same URL again, you’ll get a 404 response because the contact with that ID no longer exists.

{
    "id": 1,
    "firstName": "Kai",
    "lastName": "Doe"
}

Sounds good, right? But what if we want to write all those operations ourselves? We can do that, too. We can define our own routes and controller actions and write whatever logic we need. That’s what we did when we defined a root route that logs the result of the Contact.find() method.

Of course, Blueprint provides much more than this simple example. It saves us from writing repetitive code, and it can be useful when we’re building an API quickly. But I don’t think it’s essential to the framework itself. If we don’t need it, we can disable Blueprint routes in config/blueprints.js.

We don’t have a config/blueprints.js file yet. Let’s create one.

touch config/blueprints.js

Open blueprints.js and write the following code.

module.exports.blueprints = {
    actions: false,
    rest: false,
    shortcuts: false,
};

These options allow us to disable different types of Blueprint routes:

  • actions disables action shadow routes.
  • rest disables REST blueprint routes.
  • shortcuts disables shortcut routes.

With these options set to false, Sails won’t automatically create those Blueprint routes. Our explicitly defined routes will continue to work.

One thing worth mentioning: shortcut routes are intended for development convenience and should not be enabled in production. They allow us to perform operations through URLs without writing our own routes and controllers.

---

Sails comes with a built-in asset pipeline. It uses Grunt to process assets and move them into the directory from which they can be served to the browser. This is very useful when we want to compile Sass into CSS, combine JavaScript files, or minify assets before deploying our application.

But those days are mostly gone. Have you witnessed many new projects using the traditional approach, where we build a web application using an MVC framework, write SCSS alongside our views, or use CoffeeScript instead of JavaScript? Nowadays, backends are often used to provide APIs, while frontend applications are developed separately. And even if you disagree with that approach, the ecosystem has moved towards other solutions such as Webpack, Vite, esbuild, Parcel, and so on.

I don’t have a problem with Grunt or similar solutions. I personally like these tools and technologies. The problem is that developers are moving quickly towards newer solutions. So quickly that many developers don’t want to learn older, but proven and mature, technologies.

I’ve witnessed projects that were beautifully written, well-maintained, and used for a long time, only to see the company plan to rewrite them using the next popular technology because it was becoming difficult to find developers who knew the existing technology.

Again, we can use a separate tool to manage our frontend assets. We can use Vite, Webpack, or any other tool we prefer. We can even keep our frontend application in a separate project.

If our Sails application only provides an API to a React, Vue, or mobile application, we might not need the built-in asset pipeline at all. Sails allows us to disable the Grunt hook. For example, we can add the following configuration to .sailsrc.

{
    "hooks": {
        "grunt": false
    }
}

This disables the built-in Grunt hook. We can now manage asset compilation outside Sails.

One thing to remember: disabling the hook also means Sails won’t perform the asset processing that the hook normally handles. If our application depends on those tasks, we need to replace them with another solution.

---

Realtime communication is another feature Sails provides out of the box. Sails uses Socket.IO to provide realtime communication between the server and clients. It uses WebSocket when possible and provides additional functionality on top of it. For example, imagine we're building a chat application. When one user sends a message, we want to deliver it to other connected users without asking them to refresh the page. Realtime communication is useful for this kind of application.

But what if we're building a simple REST API? Do we need WebSocket support? No. We can build the entire application using regular HTTP requests. For example, we can define a route that returns a list of contacts and consume it using fetch() from the frontend. There is no need to establish a persistent socket connection.

If we don't need socket support, we can disable the sockets hook through .sailsrc.

{
    "hooks": {
      "sockets": false
    }
}

This disables Sails' built-in socket functionality. Our regular HTTP routes can still do their job.

I like the fact that Sails provides realtime communication without requiring us to build everything from scratch. But I also think it should remain optional. Not every application needs it.

---

Now let's talk about Node Machines. Node Machines provide a standard way to define reusable functions, describe their inputs, and specify their possible outcomes. The idea is to make functions more predictable and easier to reuse across projects. For example, imagine we want to add two numbers. We can write a regular JavaScript function.

function add(a, b) {
    return a + b;
}

That's perfectly fine. We don't need a framework to add two numbers! But let's say we want to define the inputs and describe the possible outcomes in a standard way. We can use the machine package.

You don't have to install the package or write the code yourself. It's here just for explanation.

First, install it.

npm install machine

Now let's define our function as a machine.

const Machine = require("machine");

const add = Machine.build({
    identity: "add",

    inputs: {
        a: {
            type: "number",
            required: true,
        },
        b: {
            type: "number",
            required: true,
        },
    },

    exits: {
        success: {
            outputType: "number",
        },
    },

    fn: function (inputs, exits) {
        return exits.success(inputs.a + inputs.b);
    },
});

The function now has a defined interface: it expects two numbers and returns their sum through the success exit. This is a small example, but the same idea can be useful when building larger reusable operations.

This looks good in theory, especially in a small example where the code itself explains everything. But would you write this much code when building an application? Do you think this JavaScript code is readable? I don't think so. We can use ordinary JavaScript functions, promises, and async/await instead.

Node Machines are an additional programming model. Some developers might like the structure they provide, while others might prefer plain JavaScript. Personally, I'd rather have this kind of functionality available as a separate package instead of making every developer learn it as part of the framework.

---

Internationalization, often shortened to i18n, is the process of preparing an application to support multiple languages and locales. For example, we might want to show Hello, world! to English-speaking users and નમસ્તે, દુનિયા! to Gujarati-speaking users. We could write our own translation functions and store translations in JSON files. Alternatively, we could use an existing internationalization package. Sails provides this functionality out of the box.

Let's see how it works.

First, let's create a config/i18n.js file and define the languages our application supports.

touch config/i18n.js

Open config/i18n.js and write the following code.

module.exports.i18n = {
    locales: ["en", "gu"],
    defaultLocale: "en",
};

We're telling Sails that our application supports English (en) and Gujarati (gu). English is our default language. Now, we need to provide the translations. Sails looks for translation files in the config/locales directory by default. Let's create two files: en.json and gu.json.

In the en.json file, write the following:

{
    "hello": "Hello, world!"
}

Write the following in the gu.json file.

{
    "hello": "નમસ્તે, દુનિયા!"
}

Notice that both files use the same key, hello, but provide different translations.

Now let's open our views/home.ejs file and replace its existing heading with the following code.

<h1><%= __('hello') %></h1>

That's it! We don't need to write our own translation function. Sails provides the __() function, which looks up the translation for the current locale. Restart the application and visit the home page. By default, you should see the English greeting. If the browser sends a Gujarati language preference, Sails can display the Gujarati greeting instead.

We can test this using curl, too.

curl -H "Accept-Language: gu" http://localhost:3000/

The response should contain the Gujarati greeting, assuming the root route renders our home.ejs view.

We can also explicitly change the locale for a particular request using req.setLocale(). For example, if we want to force Gujarati for the current request, we can do this in a controller action before rendering the view.

module.exports = {
    index: async (req, res, next) => {
        const contacts = await Contact.find();
        console.log("contacts:", contacts);

        const logs = await Log.find();
        console.log("logs:", logs);

        req.setLocale("gu");
        res.render("home");
    },
};

This is useful when we want to let users choose their preferred language instead of relying entirely on their browser settings. But does every application need internationalization? Probably not. If we're building an internal API that only returns language-neutral data, we might not need translation support at all.

I don't think internationalization is a bad feature to have. I just think that it should be possible to use it when we need it, without making it a requirement for every application.

---

We've looked at several functionalities that Sails provides and discussed whether they need to be part of the framework. Some of them, such as Blueprint, can save us from writing repetitive code. Others, such as realtime sockets and asset compilation, are useful only for certain kinds of applications. Node Machines provide another way to structure reusable functions, while internationalization helps us support users who speak different languages.

I don't think there's a single correct answer to whether these features belong in Sails. Framework design involves trade-offs, and different developers will make different choices. What matters to me is that we understand what the framework provides, why it provides it, and whether we actually need those features in our applications. After all, we're using a framework to make our work easier, not to give ourselves more things to learn without a good reason.

And that's exactly why one should understand the framework beyond simply using it!


Chapter 02: Customizing Sails Apps

—

In the previous chapter, we created a small Sails application and explored the basics of its MVC functionality. We saw how to define routes, create controllers and models, and render a view.

In this chapter, we’ll explore some ways to customize a Sails application. We’ll start with simple changes, such as changing the port, and gradually move towards customizing the functionalities that Sails provides.

This chapter is part of my book, Understanding and Using Sails.js, which I'm writing and publishing chapter by chapter.

You can read the first few chapters for free on this blog. Visit the book's dedicated page for more information, or head over to Gumroad to purchase a copy and/or support the ongoing writing of this book.

The idea is to understand how much control Sails gives us over the application without requiring us to modify the framework itself.

---

Let’s start by changing the port. By default, Sails starts the server on port 1337. But what if we want to run our application on a different port? We can pass configuration options to the sails.lift() method in the app.js file. Let’s change the port to 3000.

const sails = require('sails');

sails.lift({
    port: 3000,
});

That’s it! Restart the application, and Sails will start the server on port 3000 instead of the default 1337 port. You can now visit http://localhost:3000 in your browser to access the application. This is a simple example of how we can customize a Sails application by passing configuration options when lifting the server.

---

If you’ve run a Sails application, you’ve probably noticed the ASCII ship displayed in the terminal when the application starts. It’s a nice touch, but we might not always want to see it. For example, we may prefer cleaner console output when running our application. Sails provides the log.noShip option for this purpose. Let’s update the code in our app.js file.

const sails = require('sails');

sails.lift({
    port: 3000,
    log: {
        noShip: true,
    },
});

We’ve added the log configuration object and set noShip to true. Now, Sails won’t display the ship during startup. Notice that we’re still using the same sails.lift() method. We’re simply passing an additional configuration option. Restart the application, and the ship should disappear from the console!

---

When starting the application, you might notice some messages related to sessions in the console. If our sandbox application doesn’t need session support, we can disable the session hook.

When starting the application, you might notice following messages related to sessions in the console. If our sandbox application doesn’t need session support, we can disable the session hook.

debug: Warning: no session secret was set!  In development mode, we'll set this for you,
debug: but session secret must be manually specified in production.
debug: To set up a session secret, add or update it in `config/session.js`:
debug: module.exports.session = { secret: 'extremely-secure-keyboard-cat' }
debug: 
debug: (Or if you don't need sessions enabled, disable the hook.)

Sails uses hooks to organize and extend its functionality. A hook can provide a particular capability to the application, and Sails loads its built-in hooks during startup. Let’s disable the session hook by setting hooks.session to false in the app.js file.

const sails = require('sails');

sails.lift({
    port: 3000,
    log: {
        noShip: true,
    },
    hooks: {
        session: false,
    },
});

We’ve now disabled the session hook in addition to hiding the ship. Keep in mind that disabling the session hook is not merely a way to hide console messages. It also disables session functionality, which may be required by parts of your application. For our experimental sandbox, we can disable it if we don’t need sessions for now. With these options in place, the startup output should be much quieter.

---

Now that we’ve reduced the default startup output, let’s print a message of our own. The sails.lift() method accepts a callback that runs after the application has finished lifting. We can use this callback to print a message when the application is ready.

const sails = require('sails');

sails.lift(
    {
        port: 3000,
        log: {
            noShip: true,
        },
        hooks: {
            session: false,
        },
    },
    () => {
        console.log('Application is up and running on port 3000');
    }
);

We’ve moved the configuration options into the first argument and added a callback as the second argument. Once Sails has finished starting the application, the callback prints our message to the console. If you’re thinking about Express’s app.listen() callback function, you’re right!

---

In the previous example, we used Node’s built-in console.log() method to print our message. However, Sails provides its own logger through sails.log. It supports different log levels, such as info, warn, error, and debug.

Let’s use the info method instead.

const sails = require('sails');

sails.lift(
    {
        port: 3000,
        log: {
            noShip: true,
        },
        hooks: {
            session: false,
        },
    },
    () => {
        sails.log.info();
        sails.log.info('Application is up and running on port 3000');
        sails.log.info();
    }
);

Using the Sails logger keeps our messages within the application’s logging system. This also means that the logger’s configuration, including its log level, can affect which messages appear in the console. You may notice that we’re calling sails.log.info() without any arguments before and after our message. These calls are intended to add some visual spacing around the message.

---

We can make our startup message more informative by including the version of Sails we’re running. Sails exposes its version through sails.version.

Let’s add it to our startup output.

const sails = require('sails');

sails.lift(
    {
        port: 3000,
        log: {
            noShip: true,
        },
        hooks: {
            session: false,
        },
    },
    () => {
        sails.log.info();
        sails.log.info('Application is up and running on port 3000');
        sails.log.info(`Sails v${sails.version}`);
        sails.log.info();
    }
);

Running the application should print the version of Sails we’re using in our application.

---

So far, we’ve been passing all our configuration options directly to sails.lift(). This works, but imagine that we have many more options to configure. Our app.js file could become cluttered with configuration details. Fortunately, Sails supports a .sailsrc file for application configuration. We can move our configuration options into this file and keep app.js clean.

Create a .sailsrc file in the root of the contacts application and add the following configuration:

{
    "port": 3000,
    "log": {
        "noShip": true
    },
    "hooks": {
        "session": false
    }
}

Now we can remove the configuration options from the sails.lift() method, keeping only the callback function in app.js. To load the configurations from the .sailsrc file, we need to import rc from sails/accessible/rc and then call the rc() function with sails as the argument.

All in all, we need to write the following code:

const sails = require('sails');
const rc = require('sails/accessible/rc');

sails.lift(rc("sails"), () => {
    sails.log.info('Application is up and running on port 3000');
    sails.log.info(`Sails.js v${sails.version}`);
});

Go ahead and restart the application. You should see the same output in the console.

---

We can rearrange the locations of models, views, and controllers. Instead of keeping these folders in their current locations, we can have a root-level app folder and put all three folders inside it:

  • app/models
  • app/views
  • app/controllers

To do this, we need to set the values of paths.models, paths.views, and paths.controllers in the .sailsrc file.

Let’s update the configuration:

{
    "port": 3000,
    "log": {
        "noShip": true
    },
    "hooks": {
        "session": false
    },
    "paths": {
        "controllers": "app/controllers",
        "models": "app/models",
        "views": "app/views"
    }
}

Now, move the controllers, models, and views into the app folder. In our existing project, this means moving the relevant folders out of api and into app. After that, rerun the application. You shouldn’t notice any difference in the result. You’ll still see the same output, but with a different folder structure.

Better? :)

---

Let’s shift our focus and customize the Sails application beyond its configuration options. For example, what if I want to use Pug as the template engine instead of EJS? First, we need to install the consolidate package.

npm i -E consolidate

As we’re planning to use Pug, don’t forget to install that as well.

npm i -E pug

View configuration is defined in the config/views.js file. We don’t have this file yet, so let’s create it.

touch config/views.js

Open config/views.js and add the following code:

module.exports.views = {
    extension: 'pug',

    getRenderFn: function () {
        const consolidate = require('consolidate');
        return consolidate.pug;
    },

    layout: false,
};

First, I specified the template extension so that I don’t have to write the extension when using the template with the res.render() method.

Second, I need to tell Sails which render function to use for the given template. If the template engine is supported by Consolidate, it’s quite easy to do: we can return the appropriate render function from getRenderFn(), as Sails suggests.

Finally, Pug has its own way of handling layouts and related functionality. We can explicitly configure the layout behavior, so I’ve set layout to false.

---

Let’s replace the disk-based database with PostgreSQL. For this, you should have PostgreSQL installed on your computer.

First, install the PostgreSQL adapter:

npm i -E sails-postgresql

Next, create the config/datastores.js file.

module.exports.datastores = {
    default: {
        adapter: require('sails-postgresql'),
        url: 'postgres://postgres:password@localhost:5432/contacts',
    },
};

The database URL follows this format:

protocol://user:password@host:port/database

Create a database named contacts in PostgreSQL and then rerun the application. With an appropriate migration strategy configured, Sails can create the contacts table in the contacts database. I know, this looks a bit odd: we have a database named contacts and a table named contacts inside it!

---

Interestingly enough, we can use multiple databases in a single application. For example, we can use MongoDB alongside PostgreSQL.

Let’s install the MongoDB adapter:

npm i -E sails-mongo

Next, add a mongo datastore to config/datastores.js.

module.exports.datastores = {
    default: {
        adapter: require('sails-postgresql'),
        url: 'postgres://postgres:password@localhost:5432/contacts',
    },
    mongo: {
        adapter: require('sails-mongo'),
        url: 'mongodb://localhost:27017/logs',
    },
};

We’ve now configured two datastores: default, which uses PostgreSQL, and mongo, which uses MongoDB.

Let’s create a Log model to work with the MongoDB datastore in Log.js file within app/models.

touch app/models/Logs.js

Open Logs.js and add the following code.

module.exports = {
    datastore: 'mongo',
    attributes: {
        id: {
            columnName: '_id',
            type: 'string',
        },
        action: { 
            type: 'string', 
            required: true,
        },
  },
};

We’ve specified mongo as the datastore for this model. This tells Waterline to use our MongoDB datastore when working with Log.

Let’s use both models in index function of HomeController.js file.

module.exports = {
    index: async (req, res, next) => {
        const contacts = await Contact.find();
        console.log('contacts:', contacts);

        const logs = await Log.find();
        console.log('logs:', logs);

        res.render('home');
  },
};

Here, we’re querying the Contact and Log models separately. The Contact model uses the default PostgreSQL datastore, while the Log model uses MongoDB. This is a simple example of how we can work with multiple databases in the same Sails application.

---

Customization is endless! But let’s stop here. In the next chapter, we’ll take a look at some extra functionalities that Sails provides. I said extra because I personally think these are nice-to-have features that don’t necessarily need to be part of the framework. Again, that’s just my personal opinion, and you might disagree. That’s totally fine with me! :)


હઠયોગ

— ,

हकारः कीर्तितः सूर्यः ठकारश्चन्द्र उच्यते।
सूर्याचन्द्रमसोर्योगाद्धठयोगो निगद्यते॥
~ सिद्धसिद्धान्तपद्धति, ગોરખનાથ

હ વર્ણ સૂર્ય તરીકે ઓળખાય છે, ઠ વર્ણ ચંદ્ર ઓળખાય છે. આ સૂર્ય અને ચંદ્રનો યોગ-સમન્વય થવાને લીધે હઠયોગ કહેવાય છે.

ત્રણ નાડીઓ ઈડા નાડી ચંદ્રનાડી તરીકે ઓળખાય છે, જયારે પિંગળાનાડી સૂર્યનાડી તરીકે ઓળખાય છે. આ બંનેનું મિલન એટલે કુંડલિની જાગરણ.

ચાર પ્રકારના યોગમાં હઠયોગ બીજા પ્રકારે આવે છે.

मन्त्रयोगो लयश्चैव हठयोगस्तृतीयकः ।
चतुर्थो राजयोगः स्यात् स द्विधा परिकीर्तितः ॥
~ શિવ સંહિતા

મંત્રયોગ, હઠયોગ, લયયોગ, અને ચોથો દ્વેત વગરનો રાજયોગ એમ ચારયોગ છે.


જ્ઞાનની સાત ભૂમિકા

—

જ્ઞાનની સાત ભૂમિકા છે:

૧. શુભેચ્છા (મોક્ષની ઈચ્છા)
સંસારથી વૈરાગ્ય પામીને મોક્ષ મેળવવાની અને સત્યને જાણવાની તીવ્ર ઈચ્છા.
આ પ્રથમ અવસ્થામાં સાધકને સંસારના ભૌતિક સુખોની નશ્વરતા સમજાય છે. તેના મનમાં "હું કોણ છું? આ જગત શું છે?" તે જાણવાની તીવ્ર જિજ્ઞાસા જાગે છે. તે અજ્ઞાનમાંથી બહાર આવવા માટે સત્સંગ, સદગુરુની શોધ અને આધ્યાત્મિક પુસ્તકોના વાંચન તરફ વળે છે. આ અવસ્થામાં વૈરાગ્યનો જન્મ થાય છે.

૨. વિચારણા (ઊંડાણપૂર્વકનું ચિંતન):
સદગુરુ અને શાસ્ત્રોના શરણે જઈને શ્રવણ, મનન અને આત્મજ્ઞાન પર ગહન ચિંતન કરવું.
શુભેચ્છા જાગ્યા પછી, સાધક ગુરુના શરણે જાય છે અને વેદાંત શાસ્ત્રોનું શ્રવણ કરે છે. ત્યારબાદ તે સાંભળેલા જ્ઞાન પર મનન-ચિંતન અને ધ્યાન કરે છે. શું સત્ય (આત્મા) છે અને શું અસત્ય (જગત) છે તેનો ભેદ પારખવાની ક્ષમતા આ અવસ્થામાં વિકસે છે.

૩. તનુમાનસા (મનની સૂક્ષ્મતા):
મનનું અત્યંત સૂક્ષ્મ થવું અને ઈન્દ્રિયો તથા વાસનાઓનો નાશ થવો.
સતત વિચારણા અને ધ્યાનના પરિણામે સાધકનું મન અત્યંત સૂક્ષ્મ અને એકાગ્ર બને છે. ઇન્દ્રિયોના વિષયો પ્રત્યેની આસક્તિ અત્યંત તનુ એટલે ક્ષીણ થઈ જાય છે. મન બાહ્ય જગતમાંથી હટીને અંતર્મુખી બને છે. આ અવસ્થા સુધી પહોંચેલો સાધક યોગાભ્યાસી કહેવાય છે.

૪. સત્ત્વાપત્તિ (સત્વગુણની પ્રાપ્તિ અને આત્મસાક્ષાત્કાર):
મનની સંપૂર્ણ શુદ્ધિ થવી અને આત્મસ્વરૂપનો સાચો પ્રકાશ (સત્ય) અનુભવવો.
આ અવસ્થામાં રજોગુણ અને તમોગુણ સંપૂર્ણપણે નાશ પામે છે અને માત્ર શુદ્ધ સત્વગુણ જ રહે છે. સાધકને આત્મસ્વરૂપનો સાચો પ્રકાશ દેખાય છે. તેને બ્રહ્મનું વાસ્તવિક જ્ઞાન (આત્મસાક્ષાત્કાર) થાય છે. આ અવસ્થામાં જગત એક સ્વપ્ન જેવું ભાસે છે. અહીં સાધક 'બ્રહ્મવિદ્' કહેવાય છે.

૫. અસંશક્તિ (સંપૂર્ણ અનાસક્તિ):
જગતના કોઈપણ પદાર્થ, કર્મ કે ફળ પ્રત્યે કોઈ જ આસક્તિ ન રહેવી (સંપૂર્ણ નિર્લેપ અવસ્થા).
આત્મજ્ઞાન થયા પછી સાધક જગતના વ્યવહારોમાં રહેવા છતાં કોઈ જ પ્રકારની આસક્તિ કે મોહ ધરાવતો નથી. કર્મ કરવા છતાં કર્મના ફળ પ્રત્યે તેને કોઈ લેપ લાગતો નથી. તે કમળના પાન પર રહેલા જળના બિંદુની જેમ સંસારમાં અલિપ્ત રહે છે. આ અવસ્થા પ્રાપ્ત કરનાર જ્ઞાની 'બ્રહ્મવિદ્વર' કહેવાય છે.

૬. પદાર્થભાવની (પદાર્થોના અસ્તિત્વનો અભાવ):
બાહ્ય પદાર્થોનું અસ્તિત્વ ભૂલાઈ જવું અને અન્ય દ્વારા જગાડવા પર જ વ્યવહારમાં જોડાવું.
આ અવસ્થામાં જ્ઞાનીનું મન બ્રહ્મમાં એટલું લીન થઈ જાય છે કે તેને બાહ્ય જગતના પદાર્થોનું કોઈ ભાન રહેતું નથી. આજુબાજુ શું બની રહ્યું છે તેનો તેને કોઈ ખ્યાલ હોતો નથી. જ્યારે અન્ય લોકો (શિષ્યો કે ભક્તો) તેને જગાડે કે પ્રેરિત કરે, માત્ર ત્યારે જ તે શારીરિક જરૂરિયાત પૂરતો વ્યવહાર કરે છે. આવા મહાત્મા 'બ્રહ્મવિદ્વરીયાન' કહેવાય છે.

૭. તુર્યગા (તુરીયાવસ્થા):
અદ્વૈત અને પરમ શાંતિની સર્વોચ્ચ અવસ્થા, જ્યાં ભેદભાવ સંપૂર્ણ નષ્ટ થઈ જાય છે અને વ્યક્તિ 'જીવનમુક્ત' બની જાય છે.
આ જ્ઞાનની સર્વોચ્ચ અને અંતિમ અવસ્થા છે. તેને 'જીવનમુક્તિ' કહેવામાં આવે છે. આ અવસ્થામાં 'હું' અને 'તું' કે 'બ્રહ્મ' અને 'જગત' એવો કોઈ ભેદ રહેતો નથી; બધું જ એકાકાર થઈ જાય છે. અન્ય લોકોના જગાડવા છતાં પણ તેઓ સમાધિમાંથી બહાર આવતા નથી. તેઓ દેહમાં હોવા છતાં વિદેહ (દેહરહિત) અવસ્થાનો અનુભવ કરે છે. આવા સિદ્ધ પુરુષ 'બ્રહ્મવિદ્વરિષ્ઠ' કહેવાય છે.

આ જ્ઞાનની ભૂમિકાની આવસ્થાને આધારે યોગી કઈ કક્ષાએ છે, એ નક્કી થાય છે અને જરૂરી માર્ગદર્શન અથવાતો ઉપાધિ જેવી કે, યોગી, ઋષિ, મહર્ષિ, પરમહંસ વગેરે પામે છે.


Chrome અને Edge XSLT કાઢી રહ્યા છે

—

Chrome અને Microsoft Edge જેવા બ્રાઉઝર માં લાંબા સમયથી રહેલી XSLT સુવિધા દૂર થવાની તૈયારીમાં છે. XSLTનો ઉપયોગ XMLમાં રહેલા ડેટાને HTML જેવા બીજા સ્વરૂપમાં ફેરવવા માટે થતો હતો. ઉદાહરણ તરીકે, કોઈ વેબસાઇટ XML ફાઇલમાં માહિતી રાખે અને browser XSLTની મદદથી તેને વાંચી શકાય તેવા પેજમાં ફેરવે. Googleના જણાવ્યા મુજબ, Chrome 158માં 17 નવેમ્બર, 2026થી સામાન્ય વપરાશકર્તાઓ માટે આ સુવિધા બંધ કરવાની યોજના છે.

આ ફેરફાર પાછળનું મુખ્ય કારણ સુરક્ષા અને ઘટતો ઉપયોગ છે. Chromeમાં XSLT માટે વપરાતી જૂની libxslt લાઇબ્રેરી જટિલ છે અને તેમાં સુરક્ષાની ખામીઓ મળી આવી છે. આજે મોટા ભાગની વેબ એપ્લિકેશન્સ ડેટા મેળવવા માટે JSON અને પેજ બનાવવાં JavaScriptનો ઉપયોગ કરે છે, તેથી browserમાં XSLTની જરૂર પહેલાં કરતાં ઘણી ઓછી રહી છે.


ફિલાડેલ્ફિયામાં ડિલિવરી રોબોટનો રસ્તો રોકવા બદલ મહિલાની ધરપકડ

—

ફિલાડેલ્ફિયાના સેન્ટર સિટીમાં કેરોલિન માર્વિન નામની મહિલાની ડિલિવરી રોબોટનો રસ્તો રોકવા બદલ ધરપકડ કરવામાં આવી છે. પોલીસના જણાવ્યા મુજબ, 1200 બ્લોક ફિલબર્ટ સ્ટ્રીટ પાસે માર્વિને વારંવાર રોબોટનો રસ્તો અટકાવ્યો અને અધિકારીઓની ખસવા અંગેની સૂચનાઓનું પાલન ન કર્યું. તેમના પર અવ્યવસ્થિત વર્તનનો આરોપ મૂકાયો છે.

માર્વિન કહે છે કે તેઓ રોબોટ્સનો વિરોધ કરે છે, કારણ કે તેમના મતે તે કામદારોની નોકરીઓ લઈ રહ્યા છે. તેઓ આ વિરોધને જાહેર અભિવ્યક્તિનો અધિકાર માને છે અને આગળ પણ વિરોધ ચાલુ રાખવાની વાત કરે છે. આ ઘટનાનો વીડિયો ઑનલાઇન વાયરલ થયો છે.

~ સ્ત્રોત - 6abc.com સમાચાર.


ChatGPT કઈ રીતે કામ કરે છે?

—

તમે ChatGPT ને પ્રશ્ન પૂછો છો અને તે તરત જ જવાબ આપી દે છે. પણ શું તમે ક્યારેય વિચાર્યું છે કે અંદર ખરેખર શું થાય છે? ઘણા લોકો માને છે કે ChatGPT એક વિશાળ જ્ઞાનકોશ છે — જેમાં દરેક પ્રશ્નનો જવાબ પહેલેથી ભરેલો છે. જ્યારે તમે પ્રશ્ન પૂછો, ત્યારે તે યોગ્ય ખાનું ખોલીને જવાબ કાઢી આપે છે.

પણ સાચી વાત એથી ઘણી અલગ છે. ChatGPT પાસે કોઈ જવાબોની યાદી નથી. તે દરેક જવાબ ત્યારે જ, ત્યાં જ, શબ્દ-દર-શબ્દ બનાવે છે.

આ લેખમાં આપણે સમજીશું કે આ ખરેખર કેવી રીતે થાય છે સાદી ભાષામાં ટેકનિકલ પળોજણમાં નહીં પડીએ.

ચાલો એક ઉદાહરણથી શરૂઆત કરીએ. જ્યારે તમે ChatGPT માં લખો છો કે,

"France ની રાજધાની શું છે?" ત્યારે ChatGPT ને આ "પ્રશ્ન" દેખાતો નથી. તેને ફક્ત આ શબ્દો દેખાય છે:

France / ની / રાજધાની / શું / છે / ?

અને પછી તે પોતાને પૂછે છે: "આ શબ્દો પછી સૌથી સંભવિત આગળનો શબ્દ કયો હશે?" તે અનુમાન કરે છે: "The" આ ઉપરથી હવે શબ્દોની શ્રેણી બની:

France / ની / રાજધાની / શું / છે / ? / The

ફરી પૂછે છે: "હવે આગળનો શબ્દ?" પાછું અનુમાન કર્યું કે સંભવિત શબ્દ હશે: "capital".

France / ની / રાજધાની / શું / છે / ? / The / capital

અને અંતે બને છે:

"The capital of France is Paris."

બસ, આટલું જ.ChatGPT એ ક્યાંય નક્કી નથી કર્યું કે "મારે આ પ્રશ્નનો જવાબ આપવો છે." તેણે ફક્ત શબ્દ પર શબ્દ જોડ્યા — અને પરિણામ જવાબ જેવું લાગ્યું.

જો તમને પ્રશ્ન થાય કે આ રીતે જવાબ સાચો કેવી રીતે આવે તો એનો જવાબ છે - તાલીમ. ChatGPT ને ઈન્ટરનેટના અબજો લખાણ પર તાલીમ આપવામાં આવી છે. એ લખાણમાં અસંખ્ય વાર એવું બન્યું છે કે: રશ્ન પછી → જવાબ આવ્યો. તેથી જ્યારે મોડેલને પ્રશ્ન દેખાય, ત્યારે તેના માટે "સૌથી સંભવિત આગળનો ભાગ" જવાબ જ હોય છે.

તમને દેખાય છે: એક પ્રશ્ન, પછી એક જવાબ મોડેલને દેખાય છે: શબ્દોની એક લાંબી શ્રેણી, જે તેને ફક્ત લંબાવવાની છે. તમને લાગે છે કે તેણે "જવાબ આપ્યો." પણ ખરેખર તેણે ફક્ત "લખાણ ચાલુ રાખ્યું છે".

તમારા ફોનમાં જે autocomplete છે તે યાદ છે?

તમે લખો: "હું ઘરે જાઉં..."

ફોન સૂચવે: "...છું"

બસ, ChatGPT એ જ autocomplete છે — પણ:

  • તમારા ફોન કરતાં લાખો ગણું વધુ તાલીમ પામેલું
  • સેકન્ડના લાખો શબ્દો ની ઝડપે ચાલે છે
  • આખું ઈન્ટરનેટ વાંચી ચૂક્યું છે

એટલે જ તમારો ફોન "છું" સૂચવે છે, જ્યારે ChatGPT આખો નિબંધ લખી નાખે છે. સિદ્ધાંત એક જ છે — ફક્ત સ્કેલ વિશાળ છે.

અહીં તમારે ખાસ યાદ રાખવાનું છે કે, ChatGPT અર્થ સમજતું નથી. તે ફક્ત એ જાણે છે કે "આ શબ્દો પછી, સામાન્ય રીતે આ શબ્દો આવે છે."

એટલે જ તે:

  • ખોટી માહિતી પણ આત્મવિશ્વાસથી કહી શકે
  • વિરોધાભાસી વાતો કરી શકે
  • નકલી તથ્યો (references, તારીખો, નામ) બનાવી શકે

કારણ સાદું છે: તે "સંભવિત શબ્દો" જોડે છે, "સાચા શબ્દો" નહીં. મોટા ભાગે સંભવિત અને સાચું એક જ હોય છે — તેથી જવાબ સાચો આવે છે. પણ જ્યાં સંભવિત અને સાચું અલગ પડે, ત્યાં તે ભૂલ કરે છે. આ ઘટનાને "hallucination" (ભ્રમ) કહેવાય છે.

ChatGPTઆટલું ઉપયોગી એટલા માટે છે કે, એ autocomplete અદ્ભુત રીતે કરી જાણે છે. કેમ કે, તેણે તે લાખો લેખો, પુસ્તકો, સંવાદોમાંથી શીખ્યું છે

તેથી તેનું "સૌથી સંભવિત આગળનું" ઘણી વાર ખરેખર ઉપયોગી નીકળે છે. લખવામાં, સમજાવવામાં, અનુવાદ કરવામાં, વિચારો ગોઠવવામાં — તે મદદરૂપ થાય છે.


Daemon

—

કોમ્પ્યુટરમાં પ્રોસેસ(=process) શબ્દ આવે છે. જેનો મતલબ સરળ ભાષામાં કહીએતો એવો સોફ્ટવેર જે સતત પડદા પાછળ(=background) ચાલતો હોય અને જેને માણસના હસ્તક્ષેપ/દખલ જરૂર ના પડે. ઉદાહરણ રૂપે, mysqld, systemd, sshd, વગેરે.

આ દરેક પ્રોસેસની નામ પાછળ 'd' લાગે છે, જેનો મતલબ daemon(=ડિમન) થાય છે. આ શબ્દને સમાંતર એક બીજી શબ્દ પણ અંગ્રેજીમાં છે અને એ છે demon(=ડિમન) એટલે દૈત્ય થાય છે. આ શબ્દ ગ્રીક શબ્દ daimon(=ડિમોન) પરથી ઉતરી આવેલ છે, જેનો મતલબ અલૌકિક અને દૈત્ય તો ના કહી શકાય પણ એની હારનો મતલબ થાય છે. તો શું કોમ્પ્યુટરમાં પ્રોસેસ બધી દૈત્ય છે, અને સતત ચાલુ રહીને કાંડ કરે છે. તો કે, ના એવું નથી.

આ 'd' એટલે કે ડિમનનો મતલબ જ થાય છે કે, સતત માણસના હસ્તક્ષેપ વગર કામ કરતુ પ્રાણી. અહીં એ પ્રોસેસ છે એટલે આપણે એને પ્રોસેસ ડિમન કહી છે અને એ સોફ્ટવેરના નામ પાછળ 'd' લગાડીએ છીએ. આ ડિમન શબ્દ મેક્સવેલના ડિમન(=demon) પરથી આવેલો છે. મેક્સવેલએ એક કલ્પનાત્મક પ્રયોગ રજુ કર્યો. આ પ્રયોગમાં એને એક એવા બુદ્ધિશાળી પ્રાણીની વાત કરી જે અણુઓની ગતિ જોવી એને એક અથવા બીજા બોક્સમાં જવા દે. આ કામ એને સતત કરતા રહેવનું, મેક્સવેલના હસ્તક્ષેપ વગર. તો અહીં એક કલ્પનાત્મક પ્રાણી સતત ક્રિયા કરતુ રહે, એક સહાયકના રૂપમાં. એવુ જ કામ આ બધી પ્રોસેસનું છે આથી આ બધી પ્રોસેસને ડિમન નામ આપવામાં આવ્યું.

એવું સાંભળવામાં આવ્યું છે (અને ખોટું પણ હોઈ શકે) કે મેલી વિદ્યામાં જે તે વ્યક્તિ ઝોળિયું-ઝાપટિયું કોઈની પાછળ એને હાનિ પહોંચાડવા મોકલે. એ પણ સતત પોતાનું કામ (બીજાને હાનિ પોહાચાડવાનું) કરતુ રહે એને પણ હસ્તક્ષેપની જરૂર ના પડે. તો આ ઝોળિયું-ઝાપટિયું પણ daemon(=ડિમન) જ થયુંને?!


ફ્રાન્સમાં ફ્રિજ પર સાઈબર હુમલો

—

તારીખ ૧૨ ઓક્ટોબર ૨૦૨૬ ની રાત્રે ફ્રાન્સમાં સેંકડો સુપરમાર્કેટ પર મોટો સાઈબર હુમલો થયો છે, જેના કારણે ફ્રિજ અને ફ્રીઝરના તાપમાનમાં અચાનક વધારો થયો છે. હેકર્સે ફ્રિજ અને ફ્રીઝરનું તાપમાન વધારીને ૫૦° સેલ્સિયસ સુધી કરી દીધું હતું, જેના લીધે નાની આગના બનાવો બન્યા અને લાખો યુરોની તાજી તેમજ ફ્રોઝન (બરફવાળી) ખાદ્યસામગ્રી બગડી ગઈ.

Intermarché, Coopérative U, Carrefour, Monoprix અને E. Leclerc સહિત ૪૦૦થી વધુ સુપરમાર્કેટ આનાથી પ્રભાવિત થયા છે. આના પર વધુ તપાસ ચાલી રહી છે પણ હાલ એટલી વાત બહાર આવી છે કે, ડેનિશ કંપની Danfoss ના (AK-SM 800A કંટ્રોલર જેવા) જૂના અને સૉફ્ટવેર અપડેટ વિનાના રેફ્રિજરેશન સિસ્ટમ્સની નબળાઈનો હેકર્સે લાભ લઈને આ સાઈબર હુમલો કરવામાં આવ્યો હોવાનું મનાય છે.

જાનહાનિ નથી થઈ એમ ગણી આને સામા્નયમાં નાં ખપાવી શકાય કેમ કે, આફતના સમયમાં જો ખાદ્યસામગ્રી આ રીતે સાચવીને રાખવામાં આવી હોય અને આમ સાયબર હુમલાથી એને નુકસાન કરવામાં આવે તો આફત વધુ વિકરાળ બને અને વિનાશ નોતરે.


Wallpapers

— , ,

Part of freedom in selection article series.  


ફટ્

—

તંત્રમાં ખૂબ જ પ્રખ્યાત ફટ્ નામનો રહસ્યમય ધ્વનિ, વાજસનેયીમાં જોવા મળે છે! સંહિત અપસ્તંબ શ્રૌતસૂત્રમાં, ફટ્નો ઉપયોગ અભિસારમાં કેટલીક દાંડીઓ ચઢાવવા માટે થાય છે. ~ Encyclopedia of Tantra Vol. 1 - Sadhu Santdev.

મંત્રતંત્રમાં વપરાતો શબ્દ; કર્મકાંડમાં વપરાતો શબ્દ; યજ્ઞમાં દિગ્બંધ માટે એટલે દિશાઓ તરફથી થતાં વિઘ્નો અટકાવવા માટે વપરાતા મંત્રના અંતનો અવ્યય. ~ ભગવદ્ગોમંદલ માંથી.


રોગનું દમન અને તેની લાંબા ગાળાની અસર

ડોકટરનું કામ દરદીનો રોગ કાયમને માટે દૂર કરવાનું છે. છતાં મોટા ભાગના કિસ્સામાં આવું બનતું નથી. કેટલીક વાર દવાઓ રોગને દબાવી રાખે છે. આમાં જ્ઞાનતંતુઓને નબળા બનાવી દેવાથી દુખાવો કે રોગનો સંદેશો મગજ સુધી પહોંચતો નથી. તેથી શરીરમાં રોગ હોવા છતાં રોગ અથવા દુખાવો કાયમનું ઘર કરી શરીરમાં રહે છે. દરદીને આ બાબતની કશી જાણકારી કે સંદેશો મળતો નથી.

કેટલાક દરદીઓના કિસ્સામાં રોગ જેમ જેમ જૂનો થતો જાય છે. તેમ તેમ કાયમનું ઘર બનાવી શરીરમાં જ રહી જાય છે.

~ ચક્ર શક્તિ ચિકિત્સા દ્વારા રોગ મુક્તિ, લેખક: ડૉ. રસિક છ. શાહ

Chapter 01: Sails as MVC Framework

—

We need a sandbox application in Sails framework in order to do some experiments. Let's call it contacts as it'll be a contact management system application.

The steps we're going to follow in order to create a Sails app are somewhat unusual. We're not going to use Sails CLI/Sails global package, instead will create a folder and install Sails as dependency.

This chapter is part of my book, Understanding and Using Sails.js, which I'm writing and publishing chapter by chapter.

You can read the first few chapters for free on this blog. Visit the book's dedicated page for more information, or head over to Gumroad to purchase a copy and/or support the ongoing writing of this book.

mkdir contacts
cd contacts
npm init -y
npm i -E sails

Let's create an app.js file and then write the following code.

const sails = require("sails")

sails.lift();

We first required the sails package, and then called the .lift() method to start the server on the default 1337 port.

We can run the application using node app.js or let's add this as start script in the package.json. So that, we can use the npm start command for the sake of usual practice.

{
  "name": "contacts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node app.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "sails": "1.5.18"
  }
}

Let's run the start command and wait for the ship to render in the terminal! Then you can visit http://localhost:1337 in the browser.

npm start

... some debug logs ...
 info:
 info:                .-..-.
 info:
 info:    Sails              <|    .-..-.
 info:    v1.5.18             |\
 info:                       /|.\
 info:                      / || \
 info:                    ,'  |'  \
 info:                 .-'.-==|/_--'
 info:                 `--'-------'
 info:    __---___--___---___--___---___--___
 info:  ____---___--___---___--___---___--___-__
 info:
 info: Server lifted in `D:\Playground\contacts`
 info: To shut down Sails, press <CTRL> + C at any time.
 info: Read more at https://sailsjs.com/support.

Ah! We get the 404 - Not Found error in the browser. That is expected as we don't have code against the root route. I wish instead of 404 - Not Found error Sails should render something similar to Sinatra (hint: Sinatra render a page with code snippet to copy-paste) as guided learning journey. Next.js also provide the guided journey via console errors. Nice :)

---

Let's fix 404 - Not Found error. First, we need to define the root route. In Sails, routes are defined within config/routes.js file. We don't have config folder and routes.js file. Let's create both.

mkdir config
touch config/routes.js

Open this file and write the following code.

module.exports.routes = {
    'GET /': 'HomeController.index'
};

Eventually, we'll have lot more files in this config folder.

Note: If you're in the config folder, don't just write module.exports but a module.exports.[filename] such as module.exports.routes or module.exports.globals for the globals.js file.

routes is an object with key as HTTP verb + white space + path. The value should be a controller file + dot + function name within controller. If given route is visited, a function within controller file will going to be executed.

All the controllers in Sails are within api/controllers folder. We don't have api, controllers folders, and HomeController.js file. Let's create.

mkdir api
mkdir api/controllers
touch api/controllers/HomeController.js

Open this file and write the following code.

module.exports = {
    index: (req, res, next) => {
        res.send("hello, world");
    }
};

If you know the Express then this code looks familiar to you! It is. Sails internally uses Express framework for all the base functionalities.

We now have the root route and the code to run against it. So with these changes, we are ready to run the app again. We should see hello, world text on the root route.

---

Waterline is the database ORM built by the same team who built the Sails framework. Because of that, Waterline has seamless integration supports with Sails.

Waterlines supports few databases such as MySQL, PostgreSQL, MongoDB, in-memory database via adapters. For example, sails-disk package is in-memory database adapter. I'm going to use this one for the sandbox demos. You know what, I'm going use sails-hook-orm package as it has sails-disk package within it and more so that I don't have to write any configuration code.

npm i -E sails-hook-orm

We should have a contacts table in database. To work with contacts table, we need to create a Contact model. All the models in Sails are within api/models folders. We don't have models folder and Contact.js file. Let's create.

mkdir api/models
touch api/models/Contact.js

Open this file and write the following code.

module.exports = {
    tableName: 'contacts',

    attributes: {
        id: {
            type: 'number',
            autoIncrement: true
        },
        firstName: {
            type: 'string',
            required: true
        },
        lastName: {
            type: 'string'
        }
    }
};

The code is self-explanatory. We're creating a contacts table with id, firstName, and lastName as columns with given attributes. For now, don't worry about the these attributes and their values.

We can have the Sails application without model/database support. But, If we want to use model/database support, we need to explicitly tell Sails that we're going to use database in our app. For that, we need to set models option to true in global options. The global options are defined within config/globals.js file. We don't have this globals.js file. Let's create it.

touch config/globals.js

Open this file and write the following code.

module.exports.globals = {
    models: true
};

Theoretically, this should work. But, due to some internal awkward configuration, you'll get bunch of error if you try to run the app such as - _ / lodash needs to be false, or async needs to be false, or sails needs to be true and so on. To save you with trouble, add few more configurations in globals.js file.

module.exports.globals = {
    models: true,
    _: false,
    async: false,
    sails: true
};

Now if you run the app, it will not throw any error now. Go ahead and run the app. You should see following text in the console.

Excuse my interruption, but it looks like this app
does not have a "migrate" setting configured yet.
(perhaps this is the first time you're lifting it with models?)

Tired of seeing this prompt?  Edit config/models.js.

In a production environment (NODE_ENV=production) Sails always uses
migrate:'safe' to protect against inadvertent deletion of your data.
But during development, you have a few different options:

1. FOR DEV:      alter   wipe/drop and try to re-insert ALL my data (recommended)
2. FOR TESTS:    drop    wipe/drop ALL my data every time I lift Sails
3. FOR STAGING:  safe    don't auto-migrate my data. I will do it myself

Read more: sailsjs.com/docs/concepts/models-and-orm/model-settings#?migrate
--------------------------------------------------------------------------------

What would you like Sails to do this time?
 ** NEVER CHOOSE "alter" or "drop" IF YOU ARE WORKING WITH PRODUCTION DATA **

prompt: ?:

Sails is now waiting for us to answer for the migration strategy. The possible answer we need to provide is 1, 2, or 3. Meaning of each is written in the console text. We can let's say enter 1 and press Enter to continue with our app. But, the problem is, each time you run the app, it'll ask this question again and again. To stop this, we need add our answer in config file.

The config file we need for this is config/models.js file. We don't have this globals.js file. Let's create it.

touch config/models.js

Open this file and write the following code.

module.exports.models = {
    migrate: 'alter'
};

With the config saved in config/models.js file, it'll not ask again the same question! Run the application again and you should notice the following console log somewhere.

info: ·• Auto-migrating...  (alter)
info:    Hold tight, this could take a moment.
info:  ✓ Auto-migration complete.

Perfect! contacts table is created in the database and you're ready to do the CRUD operations on this table using Contact. For example, open the HomeController.js file and add following lines within index function.

module.exports = {
    index: async (req, res, next) => {
        const contacts = await Contact.find();
        console.log("contacts:", contacts);

        res.send("hello, world");
    }
};

I've converted the function to async function and then calling Contact.find() method. Re-run the application and you should see:

contacts: []

Meaning that database is configured correctly.

One last point before we move to views. We now have the .tmp/localDiskdb/contacts.db (and archive.db) file as we've decided to choose in-memory database.

---

Instead of returning plain hello, world text let's return hello, world as h1 header. To do so, we need to create a folder with name views and then we should have an EJS/template file within it e.g. home.ejs for root route.

We don't have views folder and home.ejs file. Let's create both.

mkdir views
touch vies/home.ejs

Open this file and write the following code.

<h1>hello, world</h1>

Then we can adjust the index function in HomeController.js file to render this template instead of sending the plain hello, world text.

module.exports = {
    index: async (req, res, next) => {
        const contacts = await Contact.find();
        console.log("contacts:", contacts);

        res.render("home");
    }
};

Re-run the app again! You should see hello, world as the header text!

I think we should stop here for the Chapter 01 as we touch base the MVC concepts in Sails. I would like to highlight that Sails provide MVC functionalities out of the box with just few lines of code and some uncommon adjustment.

If we can adjust these things we have nice framework!


GTK4: Chapter 01

— ,

Let's open the text-editor with blank window. Quickly save this blank program with name hello.c. C language has .c extension and hello is the name of the program that satisfies the purpose of code, going to written inside of it. After saving this blank program, I have syntax highlighting. Good!

Next, we are going to print hello, world text in terminal. So, we need to write this text within hello.c. Let me do that.

hello, world

But, this hello, world is string, right? In program, you write string inside of double or single quotes. Agree? But in C, you must write string inside of double quotes. So, let me quickly wrap this text with double quotes and make it string.

"hello, world"

Great! This string is not going to print in terminal on its own. We need to invoke a function on it to print and the function is printf(). This is how we use printf() function.

printf("hello, world")

C is one of those languages the required semicolon to complete the statement. In C, semicolon is not optional but required. And that way I need to write,

printf("hello, world");

Semicolon at the end. Great. But you know, compiled languages like C is different than interpreted language in terms of execution. In interpreted languages, the execution start from top and execute each statement or expression that comes in flow. But, in compiled language you need to tell the starting point - a point from where the compilation process starts. This is where the main() function comes in picture. Your program must contain one main() function and this is the function from where your program will going to compile.

In order to run our printf() statement, we need to put it inside of main() function this way.

main() {
  printf("hello, world");
}

This indentation inside of function is not required. It is just for readability purpose.

You are going to use curly braces to define block like I did to define main() function block. Whatever statements we write inside of this curly braces, are considered part of the main() function.

After compilation of this main() function, it returns something. It returns an integer with 0 or greater than 0. Here, 0 means successful compilation and any other value than 0 mean something goes wrong with your program! Let's add the type signature before the main() function to indicate, what this main() is going to be written and also write return 0 explicitly.

int main() {
  printf("hello, world");

  return 0;
}

Yes, this return statement should be at the end of main() function.

In C, you don't get a single function by default and printf() is not an exception. You need to include a library in which this function is written. The library or header file is stdio.h and its standard header file that comes with C. You can include this file in our program like -

#include<stdio.h>

int main() {
  printf("hello, world");

  return 0;
}

You need to write the include statement at the top of your program and the blank line after this include statement is optional. But, again it is strongly recommended.

Note: Although I said that you have to include the stdio.h header file in order to use the printf() function, it is not completely true. You can do a little trick and still run the program (of course, with a warning). One trick is to run the program using an older standard such as C90. The command is:

gcc -std=c90 hello.c -o hello && ./hello

Do this after completing the chapter.

That’s it! This is our hello, world program in C. It's time to execute this program.

Execution

We need a compiler to execute any C program. For this purpose, we are going to use GCC. This is the compiler that comes with most of the GNU/Linux system. A quick --version command will let you know that C is installed on your system or not.

$ gcc --version
gcc (Debian 14.2.0-19) 14.2.0
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

If you get similar output, then you have GCC on your system else you need to install. In my case, I've gcc so I can go further and execute this program. The execution of C program done in two steps —

  1. Compiles the program,
  2. Executes the compiled program.

To compile the program, this command is used.

$ gcc hello.c -o hello

This will compile your hello.c program and after successful completion it will generate output (-o) with name hello. You will get nothing if compilation happen successfully in terminal. In my case, I don't get anything, so that means my program execute successfully and has build hello. A quick ls will show you the same.

$ ls
hello.c hello

In order to execute this hello executable, I need to write:

$ ./hello
hello, world

And we get the output! But, this is a little clumsy. A \n after the hello, world string will help. Let's add that.

#include<stdio.h>

int main() {
  printf("hello, world\n");

  return 0;
}

Let's compile and run the program again.

$ gcc hello.c -o hello && ./hello
hello, world

Here, I've combines both commands into single one using && operator. You will going to use so many terminal commands in this book. So, be ready!

Note: Returning 0 is important because, if a non-zero value is returned, the OS considers that something went wrong while running the program. Based on that indication/status, the OS will not continue further operations.

Let's understand this by example: I want to print in the console that program ran successfully if the program runs successfully. I can easily add an echo statement with && like this:

$ gcc hello.c -o hello && ./hello && echo "program ran successfully"

If you run this command, you should see hello, world as the output, and program ran successfully on the next line.

Now, change the return value from 0 to, let's say, 1. Run the above command again. You will not see program ran successfully.

---

If you think that I've spent a whole chapter on a topic that I considered as an assumption (yup, I assume that you should know C) about you, then you're right — I've spent the whole chapter on it. But, the idea is to give a glimpse of how the book is written. We'll write the code one or a few lines at a time, with an explanation either before or later.

Because, I personally think that each line in our program should have a reason to have it in our program. If there is a reason, I need to explain the reason unless the framework is not managing the abstraction.


સંસ્કૃતમાં च પોસ્ટફિક્સ નોટેશનમાં લખાય છે

—

કોમ્પ્યુટર વિજ્ઞાનમાં અલગ અલગ નોટેશન આવે છે - પ્રિફીક્સ, ઇનફીક્સ, અને પોસ્ટફિક્સ.

3 + 4

અહીં + કારક(=operator) છે જયારે સંખ્યા 3 અને 4 સંકાર્ય છે. કારક સંખ્યાની વચ્ચે લખાય છે એટલે એને મધ્યપ્રત્યય(=ઇનફીક્સ/infix) નોટેશન કહેવાય છે. આ નોટેશન ઘણું પ્રચલિત છે એટલે તમને આ બધે જોવા મળશે. ખાસ કરીને ગણિતમાં.

જો કારકને સંખ્યાની શરૂઆતમાં લખવામાં આવે તો એને પૂર્વપ્રત્યય(=પ્રિફીક્સ/prefix) નોટેશન કહેવાય છે. જેમ કે,

+ 3 4

આમતો આ નોટેશન ખાસ પ્રચલિત નથી, પણ લિસ્પ(=lisp) પ્રોગ્રામિંગ ભાષામાં આ ખુબ જ વપરાય છે, સંપૂર્ણ ભાષા પૂર્વપ્રત્યય નોટેશનમાં છે અને એમાં કૌંસનો બહુ ઉપયોગ થાય છે - (+ 3 4).

અને છેલ્લે આવે છે, ઉત્તરપ્રત્યય(=પોસ્ટફીક્સ/postfix). જેમાં કારક છેલ્લે લખાય છે, અને સંખ્યા પહેલા લખાય છે. જેમ કે,

3 4 +

આ નોટેશન પ્રચલિત નથી. પણ સંસ્કૃતમાં જયારે બે શબ્દો વચ્ચે અને(=and) વાપરવાનું હોય ત્યારે च(=અને) શબ્દોના અંતમાં આવે છે.

જેમ કે, ગુજરાતીમાં આપણે લખીશું/બોલીશું:

રામ અને કૃષ્ણ.

જયારે એ સંસ્કૃતમાં આ લખાશે/બોલશે:

रामः कृष्णः च |

તમે કહેશો કે આ શું? આમાં તો બધું ભેગું કર્યું - ગુજરાતી, કોમ્પ્યુટર વિજ્ઞાન અને એની ભાષા, અને સંસ્કૃત.

તો આજ ફાયદો છે, એક કરતાં વધુ વિશે જાણવાનો. તમે ગમે ત્યારે જોઈએ એ પ્રમાણે મિશ્રણ કરી શકો છે, કેમ કે તમને આવડે છે!


From Intern to Software Architect

You start your journey as an intern or junior software engineer. At this level, you care about the syntax and language constructs such as defining variables or constants correctly, writing a function that might be reusable, or constructing conditions correctly. Let's call this level - 1.

As you start to become more familiar with the language and code, your focus shifts towards the program files. Now, you start to care about files instead of language constructs as you're comfortable with syntax and language constructs. You can now connect the files across a bug fix or a change that you need to make. You're now at level - 2.

As you become more experienced, you now start to care about a module - not just a set of files but a logically related set of files. Previously, it was a set of program files only. But now you start to see program files as a module. You know the connection between files and can reason about them. You reach level - 3.

Next, you continue your journey and start seeing multiple modules all together as an app. You are not seeing the language constructs, you're not seeing a set of files, you're not seeing a module, but a set of modules all together as a whole app. You're now caring about the whole app. When you review the PRs, you are adding the whole app context to the review before you approve and merge. You're now at level - 4.

As you gain more experience, you start to see a bigger picture than the app. You're now seeing things outside of the app such as services, proxies, DNS, security, firewalls, and many surrounding services. You now not only care about the app but about these services as well, which are required for your app to be alive. You now know the exact details about how a request comes into the app and goes out through all these services. You're now at level - 5.

Your journey continues and you start to connect with other teams who manage other apps that make up the whole product. You start to see a bigger picture than the app and services. You know how apps live together, how they communicate, how they are monitored, and much more. You're now at level - 6.

I'm going to stop here at level - 6 as I think this is where the role of a software architect becomes clear and in full view. I'm not saying that the level steps stop here. But this is where I want to stop for this article discussion.

In summary, you started with syntax and reached the level where you care more about app servers, proxies, communication, security, and so on.

---

Although I've used labels level 1 - 6, you can give a position you like against the levels based on the hierarchy you have seen in organizations. For example,

Level - 1 = Intern
Level - 2 = Jr. Software Engineer
Level - 3 = Software Engineer
Level - 4 = Sr. Software Engineer
Level - 5 = Principal Software Engineer
Level - 6 = Architect

Or you can combine some levels against the same position.

Level - 1, 2 = Jr. Software Engineer
Level - 3, 4 = Software Engineer
Level - 5 = Lead Software Engineer
Level - 6 = Architect

Or you can introduce more levels. For example,

Level - 3 can be divided into two parts as Software Engineer and Sr. Software Engineer, where you write software engineering with design patterns as an effective solution in the later position.

---

Now, let's take a top-down look.

At level - 6, the architecture is created so well that at level - 5 you don't have to worry about other apps. At level - 5, the app is set up very well so that at level - 4 you don't have to worry about surrounding services. At level - 4, the app is created and maintained very well so that at level - 3, you don't have to worry about other modules within the app. This continues at level - 2, where the code is crafted so well that you don't have to worry at level - 1 that your minor code change will throw a module error or bring down the app or crash the product!

Layers of abstraction are added so well that at the given level, only the necessary context is required. When you're at level - 6, you don't have to care about the code of the app or module because it is taken care of at levels - 3 and 4. Similarly, at level - 2, you don't have to worry about other parts of the product as they are taken care of at a higher level. You just have to worry about a set of files.

---

With the advancement of AI, the border between these levels has started to become blurred. Not between levels but between the whole range of levels. You, as level - 2, are expected to work on levels - 1 to 4, or level - 4 is required to work on levels - 3 to 6. Geniuses can jump between these levels, but lack the knowledge that only comes with experience.


Subscribe to: Posts (Atom)