In Drupal 8 there are a handful of ways you can install contrib modules to your project and here we’ll discuss some of the pros and cons of each.
When installing a D8 contrib module “manually” you have to navigate to a site that hosts the module’s compressed tar.gz or zip file like at drupal.org/project/project_module or github and download the file of your choice. Next, the module has to be extracted, usually by double clicking the compressed icon, and then the new, uncompressed module folder that now appears may need to be renamed to just its machine name (for example from paragraphs-8.x-1.1 to just "paragraphs"). This module folder must then be moved into the site’s /modules directory and enabled using either Drush, Drupal Console, or the Drupal admin UI. This manual approach isn’t completely terrible because it does work and you have control over each step, but it isn’t the recommended strategy mainly because the entire module codebase has to be committed into version control which isn’t ideal. More on this in a bit.
By utilizing the extend tab in the admin UI to install a D8 contrib module, you can automate some of the steps encountered within the manual strategy but the same major problem exists - the entire codebase has to be checked into version control. It’s worth noting that this functionality allows you to point to an already downloaded tar.gz/zip file or provide a link to one hosted remotely. When the file is then uploaded, Drupal will extract the module for you and will place it into the correct directory as well. For this to work, though, you need to make sure your web user can write to the modules folder, which is generally frowned upon from a security perspective.
The previous methods definitely work and any developer should be aware of them but with D8 came the ability to use Composer to manage Drupal modules. It’s technically a dependency manager that works in much the same way as Node.JS’s "npm" and Ruby’s "Bundler."
Once installed from getcomposer.org you can find, install, update, and remove modules from the command line among other things which is a big win especially when many dependencies need updating. Also, instead of having to commit the entire codebase of the module(s) into version control, you only have to commit the manifest files (composer.json and composer.lock). The .json file lists out what dependencies the project needs and the acceptable version ranges of each while the .lock file records what versions actually got installed. It's very bad practice to commit dependencies that aren't yours into your repository and also because it increases the size of the repository significantly. To include a new dependency simply execute ‘composer require drupal/module_name’ and composer will add the module to the .json file. Then execute ‘composer install’ to get the module codebase from the Drupal packages API (NOTE: not "Packagist," which is the default Composer package respository) and to update the .lock file. Running ‘composer update’ is great because it will update all the modules to their latest versions allowed by the .json file, install them, and update the .lock file. Composer however will not enable the modules for you in Drupal, so that is where Drush can come to the rescue. Lastly, you can still use Drush or Drupal Console in D8 to install modules but composer is definitely the preferred tool because of these added benefits.