Search by Category
Close

Filter by Category

Artificial Intelligence
React
Ruby on Rails
Next.js
Python
Javascript
HTML & Frontend
CSS
CSS Tricks
Caching
Database & Designs
Authentication & Security
Web Hosting
Technology
user profile avatar

Tech Wizard

Published on • 🕑5 min


Building A REST API with Prisma, NodeJS and PostgreSQL

I once shared that I have migrated my database from Active Record using ruby on rails to a Prisma ORM and Node Js server powered by NextJS. This tutorial will create a simple API for Tesla vehicles to help you get started. Prisma is a powerful database toolkit that simplifies working with databases by providing a modern, type-safe query builder.

We'll cover the following Prisma essentials:

  1. Connecting Prisma with a Database
  2. Setting Up Prisma Migrations
  3. Seeding the Database
  4. Implementing CRUD Operations

Prerequisites

Before starting, ensure you have the following:

  • Node.js installed (Version 14 or higher)
  • A PostgreSQL database (you can create a free database with Vercel or Neon DB)

Step 1: Setting Up Your Node.js Project

The first step is to initialize a new NodeJS project in the terminal. This project will be the foundation for the REST API you will build in this tutorial. To do this, you must first create a folder and a typescript project.

mkdir tesla-api
cd tesla-api
npm init -y

After creating a new NodeJS project, we need to install other prerequisites such as typescript and Prisma CLI.

npm install typescript ts-node @types/node --save-dev
npm install express prisma @prisma/client

Lastly, we need to add a tsconfig.json file to make sure Typescript works correctly. You can create the file manually or use nano tsconfig.json and add the following code.

{
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "lib": ["esnext"],
    "esModuleInterop": true
  }
}

Step 2 — Setting Up Prisma with PostgreSQL

The second step is to initialize Prisma and create our vehicle model to map with the database. We first need to add our database URL to the .env file so that Prisma knows how to connect with the database.

nano .env

Now update the .env file to add your database url from Vercel, NeonDB, or another database provider. If you are using Ubuntu, you can also run PostgreSQL locally using pgadmin. If you are on a pooled database you also need a NON-POOLING url to be used for seeding.

//tesla-api/.env
DATABASE_URL="postgresql://johndoe:your_password@localhost:5432/my-blog?schema=public"
NON_POOLING_DATABASE_URL="postgresql://johndoe:your_password@localhost:5432/my-blog?schema=public"

Make sure to change the database credentials to the ones you specified in your hosted database or localhost database. Once you’re done, save and exit the file and initialize Prisma by running. npx prisma init.

npx prisma init
Output
âś” Your Prisma schema was created at prisma/schema.prisma.
  You can now open it in your favorite editor.

Step 3 — Defining Your Data Model and Creating Database Tables

Now that your project is initialized, it's time to define your data model. Navigate to the prisma/schema.prisma file in your project directory. Here, you'll define your database schema using Prisma's intuitive schema language.

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("NON_POOLING_DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Vehicle {
  id        Int     @id @default(autoincrement())
  model     String
  price     Float
  picture   String
  year      Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

We are defining just one model for the Vehicle with fields for model, price, picture, and year, but you can add a user model if you like. Save the changes and close the file.

With these models in place, you can now create the corresponding tables in the database using Prisma Migrate. In your terminal, run the following command:

npx prisma migrate dev --name init

This command generates a new migration file based on your data model changes. The SQL migration file in the prisma/migrations/20241209084626_init/migration.sql directory has the following statements that were executed against the database.

Step 5: Generating a Prisma client

It is often a good practice to create a global Prisma client to prevent recreating a new instance every time. To do this, we initialize Prisma Client using npx prisma generate and create a new folder where we declare a Prisma client and export the client for use in other files.

npx prisma generate

Now create a file named prisma.ts under the Prisma folder and add the following code:

//prisma.prisma.ts
import { PrismaClient } from "@prisma/client";
declare global {
    let prisma: PrismaClient | undefined;
  }
  const globalForPrisma = global as typeof global & { prisma?: PrismaClient };
  let prisma: PrismaClient;
  if (!globalForPrisma.prisma) {
    globalForPrisma.prisma = new PrismaClient();
  }
  prisma = globalForPrisma.prisma;
  export default prisma;

Step 6: Seeding the Database

Prisma allows us to seed the database if we have some seed data. To seed your database, you need to add a seed.ts file and add our seed data. In a larger project, you might need to edit the package.json to add a seed script.

//prisma/seed.js
import prisma from "../prisma/prisma"
async function main() {
  await prisma.vehicle.createMany({
    data: [
      { model: 'Tesla Model S', price: '51885.17', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MT337,$PPSW,$W40B,$IBB1&view=STUD_FRONT34&model=m3&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year: 2012 },
      { model: 'Tesla Model 3', price: '100990', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MTS10,$PPSW,$WS90,$IBE00&view=FRONT34&model=ms&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year: 2017 },
      { model: 'Tesla Model X', price: '120990', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MTX10,$PPSW,$WX00,$IBE00&view=FRONT34&model=mx&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year: 2015 },
      { model: 'Tesla Model Y', price: '65000', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MTY07,$PPSW,$WY19B,$INPB0&view=FRONT34&model=my&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year: 2020 }
    ]
  });
}

main()
  .then(() => {
    console.log('Database seeded');
  })
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

Next, run the seed script in the terminal by running the command:

node prisma/seed.js

Step 7: Create CRUD Operations

The next step is to initiate a ExpressJs server to interact with the database and perform CRUD operations.

First, create a index.ts file to add our server code.

nano src/index.ts

In the src/index.ts file, add the following code to create the API routes.

import express from "express";
import prisma from "../prisma/prisma"
const app = express();
app.use(express.json());
// Get all vehicles
app.get('/vehicles', async (req, res) => {
  const vehicles = await prisma.vehicle.findMany();
  res.json(vehicles);
});
// Get a vehicle by ID
app.get('/vehicles/:id', async (req, res) => {
  const { id } = req.params;
  const vehicle = await prisma.vehicle.findUnique({
    where: { id: parseInt(id) },
  });
  res.json(vehicle);
});
// Create a new vehicle
app.post('/vehicles', async (req, res) => {
  const { model, price, picture, year } = req.body;
  const newVehicle = await prisma.vehicle.create({
    data: { model, price, picture, year },
  });
  res.json(newVehicle);
});
// Update a vehicle
app.put('/vehicles/:id', async (req, res) => {
  const { id } = req.params;
  const { model, price, picture, year } = req.body;
  const updatedVehicle = await prisma.vehicle.update({
    where: { id: parseInt(id) },
    data: { model, price, picture, year },
  });
  res.json(updatedVehicle);
});
// Delete a vehicle
app.delete('/vehicles/:id', async (req, res) => {
  const { id } = req.params;
  await prisma.vehicle.delete({
    where: { id: parseInt(id) },
  });
  res.sendStatus(204);
});
// Start the server
app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000');
});

Test the server by running the following command

npx ts-node src/index.ts
or
node src/index.ts

You can modify the routes to add try catch blocks for error handling. You can also edit the package.json file to make it easier to run the server by adding a run script. Update the package.json file to add the following code.

"scripts": {
  "build": "tsc",
  "start": "node dist/index.js",
  "dev": "ts-node src/index.ts"
}

Now you can start the development server using npm as follows:

npm run dev

Conclusion

In this article, you created a REST API server with several different routes to create, read, update, and delete user and post data for a sample blogging application. Inside the API routes, you use the Prisma Client to send the respective queries to your database.

As next steps, you can implement additional API routes or extend your database schema using Prisma Migrate. Visit the Prisma documentation to learn about different aspects of Prisma and explore some ready-to-run example projects using tools such as GraphQL or grPC APIs in the prisma-examples repository.

 

20Blog views

68

share this blog
user profile avatar

Tech Tales Team

Published on • 🕑5 min


Common Mistakes that Beginners Often Make in React

React is a popular javascript framework that is the most loved library for building user interfaces. Despite being one of the front-end developer's favorites, React introduces pitfalls that beginners often fall into. These blogs are related to state and effects and other functionalities that make React easier to work with. Here are five common mistakes beginners often make in React, along with their solutions.

1. Mutating state

React's state is an essential concept that allows components to manage and update their data. However, directly modifying the state is a common mistake that can lead to unexpected behavior and difficult-to-debug issues.

An example of a mutating state is through the use of destructive array methods such as push to add an item to a list.

const [items, setItems] = useState([1, 2, 3]);
  // ❌ Mutates an existing object
const addItem = (item) => {
  items.push(item); // Mutating state directly
  setItems(items);
};
// âś… Creates a new object
const addItem = (item) => {
  setItems([...items, item]); // Creating a new array
};

React relies on a state variable's identity to tell when the state has changed. When we push an item into an array, we aren't changing that array's identity, and so React can't tell that the value has changed.

Instead of modifying an existing array, I'm creating a new one from scratch. It includes all of the same items (courtesy of the ... spread syntax), as well as the newly-entered item.

The distinction here is between editing an existing item, versus creating a new one. When we pass a value to a state-setter function like setCount, it needs to be a new entity. The same thing is true for objects.

2. Accessing state after changing it

State in React is asynchronous. This means that when we update the state we are not re-assigning a variable but rather scheduling a state update. Mostly common, developers will be frustrated if they console.log the new value and it is not there.

For example:

const [count, setCount] = useState(0);
const increment = () => {
  setCount(count + 1);
  console.log(count); // This will log the old count, not the new one
};

This can lead to confusion when debugging because the console might not show the expected value. It can take a while for us to fully wrap our heads around this idea, but here's something that might help it click: we can't reassign the count variable, because it's a constant!
To work with the updated state, you can use the functional form of setState, which provides the latest state:

const increment = () => {
  setCount((prevCount) => {
    console.log(prevCount + 1); // Correctly logs the new count
    return prevCount + 1;
  });
};

3. Using Functions as useEffect Dependencies 

useEffect is one of the most abused hooks and developers often use it recklessly even in areas where it is not needed. One common mistake with useEffect is failing to provide a dependency array, which leads to endless renders. 

One point to note here is not to confuse re-renders with refresh. Since React uses a Virtual DOM, re-renders happen in the back-stage and thus developers might fail to notice.

The following is a react useEffect example: 

function App(){ 
    const [data, setData] = useState(null); 
    const fetchData = () => { 
         // some code 
    } 
    useEffect(() => { 
    fetchData(); //used inside useEffect 
     }, [fetchData]) 
} 

It is not recommended to define a function outside and call it inside an effect. In the above case, the passed dependency is a function, and a function is an object, so fetchData is called on every render.

React compares the fetchData from the previous render and the current render, but the two aren't the same, so the call is triggered. 

4. Props Drilling

Props drilling in React occurs when developers pass the same props to every component one level down from its parent to the required component at the end. Thus, components become closely connected with each other and can’t be used without adding a particular prop. The fewer unnecessary interconnections between components in your code, the better.

For example, if we need to pass a user name to a deeply nested component but the name is defined in the first parent component:

const Grandparent = () => {
  const user = { name: 'Alice' };
  return <Parent user={user} />;
};
const Parent = ({ user }) => {
  return <Child user={user} />;
};
const Child = ({ user }) => {
  return <div>{user.name}</div>;
};

In this example, the user prop is passed down from Grandparent to Child through the Parent component, even though Parent doesn't need it. This kind of pattern can quickly lead to a tangled mess of props.

To avoid props drilling, consider using React Context or state management libraries like Redux, Zustand, or Recoil. Context allows you to share data across multiple components without passing props manually through every level.

// Create a context
const UserContext = createContext();
const Grandparent = () => {
  const user = { name: 'Alice' };
  return (
    <UserContext.Provider value={user}>
      <Parent />
    </UserContext.Provider>
  );
};
const Parent = () => {
  return <Child />;
};
const Child = () => {
  const user = useContext(UserContext);
  return <div>{user.name}</div>;
};

5. Changing from Uncontrolled to Controlled Inputs

Uncontrolled components rely on the DOM to manage their state, while controlled components rely on React. Beginners sometimes switch from uncontrolled to controlled components without a clear need, adding complexity without gaining any tangible benefits.

One major reason why developers switch to controlled inputs is the need for validations. However, we can validate inputs without the need for additional javascript simply using browser inbuilt functions. See more here a-better-way-to-validate-html-forms-without-usestate.

Another reason (I am often guilty of this) is when we need to implement search functionality that filters our data to return the results. However, the most efficient solution to search is to store the value in search parameters. In this way, the value will not be lost on refresh.

Developers might implement the search functionality like this:

function SearchForm() {
  const [query, setQuery] = useState(null);
  function handleSearch (e){
    e.preventDefault()
    window.location.href=`/my-site/search?q=${query}`
  }
  return (
    <form onSubmit={handleSearch}>
      <search>
        <input
          type="search"
          value={search}
          onChange={() => setQuery(e.target.value)}></input>
      </search>
    </form>
  );
}

This example receives the search input and saves it in the state. When the search form is submitted, we redirect the user to the search page with the search-params.

One simple solution is:

function SearchForm() {
  return (
    <form action="/search/query">
      <search>
        <input type="search" name="query"></input>
      </search>
    </form>
  );
}

This will work the same and the form will be submitted with the query params that can be used to filter the data. Another advantage of this setup is that if we are using NextJS, this form will work as a server component and thus benefit from faster rendering.

Conclusion

There are many mistakes that we make in React that we are often unaware of. By understanding and avoiding these common mistakes, you can write more robust and efficient React code. Remember to practice good coding practices and explore the full range of React hooks to build high-quality user interfaces.

21Blog views

242

share this blog
user profile avatar

tech girl

Published on • 🕑5 min


The Future of Quantum Computing: What to Expect in the Next Decade

Quantum computing has been a buzzword in the tech industry for years, promising to revolutionize everything from cryptography to artificial intelligence. But what does the future hold for this cutting-edge technology?

As we look ahead to the next decade, it's clear that quantum computing will move from theoretical research to practical applications, impacting industries in ways we can only begin to imagine.

Understanding Quantum Computing

At its core, quantum computing leverages the principles of quantum mechanics to perform computations that are infeasible for classical computers. Unlike classical bits, quantum bits (qubits) can exist in multiple states simultaneously, thanks to superposition and entanglement.

This allows quantum computers to process a vast amount of data in parallel, making them exponentially more powerful for certain tasks.  

Quantum computing harnesses the power of quantum mechanics to help us overcome the limitations that today’s computing world has been encountering.

Potential Applications

  1. Cryptography: One of the most anticipated applications of quantum computing is in the field of cryptography. Quantum computers have the potential to break traditional encryption methods, leading to the development of quantum-resistant cryptographic algorithms.

  2. Drug Discovery: Quantum computing could dramatically speed up the process of drug discovery by simulating molecular structures and interactions with unprecedented accuracy. This could lead to faster development of new medications and treatments for various diseases.

  3. Optimization Problems: Industries that rely on complex optimization problems, such as logistics, finance, and manufacturing, could see significant efficiency improvements. Quantum algorithms can find optimal solutions much faster than classical algorithms, reducing costs and improving performance.

Challenges of Quantum Computing

Despite remarkable advances, quantum computing still faces significant technological hurdles that limit its applications, scalability, and reliability at this stage. Addressing these challenges is crucial for realizing the full potential of quantum computing in the future.

1. Decoherence and Error Correction

One of the most pressing challenges in quantum computing is decoherence. Unlike classical computers, quantum computers are highly susceptible to noise, with qubits—quantum bits—being extremely fragile.

Even slight perturbations, such as minor vibrations or temperature fluctuations, can disrupt the quantum state of qubits, leading to the loss of stored information.

This phenomenon, known as decoherence, poses a significant obstacle because calculations must be completed before decoherence occurs.

To combat decoherence, researchers are developing error-correcting codes. While error correction is well-established in classical computing, it becomes far more complex in the quantum realm.

Quantum errors can manifest as phase shifts, bit flips, or a combination of both, making them difficult to detect and correct. Additionally, unlike classical bits, qubits cannot be copied, necessitating more sophisticated correction codes.

Techniques such as the Shor code, the Steane code, and the Surface code are widely used to encode quantum information and protect it from errors, but they require multiple physical qubits to represent a single logical qubit, complicating the overall system.

2. Scalability

Scalability is another major challenge in quantum computing. For quantum systems to tackle more complex problems, the number of qubits must be increased significantly. However, connecting a large number of qubits to create larger, more powerful quantum systems is incredibly challenging.

One potential solution lies in the use of error correction techniques, which introduce redundancy to detect and correct errors. However, implementing these techniques requires additional qubits and complex algorithms, making the scaling process even more difficult.

3. Physical Implementation

Quantum computers can be built using various methods, including superconducting circuits, trapped ions, and topological qubits. Each approach has its own set of difficulties, and it remains uncertain which method will prove most viable for large-scale quantum computing.

Superconducting circuits, for example, require extremely low temperatures to maintain coherence, while trapped ions need precise control and isolation from environmental noise.

The race to develop scalable and efficient quantum hardware continues, with each method presenting unique technical challenges that require interdisciplinary collaboration and innovation.

4. Cost and Accessibility

Currently, quantum computers are prohibitively expensive and require specialized environments, such as cryogenic cooling systems, to operate.

As a result, accessibility is a significant barrier, with only a handful of research institutions and tech companies having the resources to develop and utilize quantum computers.

One of the key challenges moving forward is to make quantum computing more accessible and affordable, enabling wider adoption and experimentation.

5. Workforce and Expertise

The field of quantum computing demands deep knowledge of quantum mechanics, computer science, and engineering.

However, finding professionals who possess expertise in all three areas is difficult, leading to a talent shortage that could slow down the development and adoption of quantum computing technologies.

Building a workforce with the necessary skills will require significant investment in education and training programs, as well as fostering collaboration between academia, industry, and government.

The Future of Quantum Computing

Despite the challenges, the future of quantum computing is incredibly promising. Technology giants such as IBM, Google, and Microsoft, along with numerous research institutions, are investing heavily in this field.

These investments are driving continuous progress in increasing qubit coherence times, reducing error rates, and developing new quantum algorithms.

As quantum computing technology matures, it is expected to become a fundamental tool for scientific research, making it possible to solve previously intractable problems.

Governments around the world are recognizing the strategic importance of quantum computing and are increasing funding for research and development. Collaborative efforts between public and private sectors are likely to accelerate breakthroughs in this area.

In the near future, it will be essential to identify use cases for quantum computing early on and manage expectations regarding its capabilities.

While initial applications may be limited, as quantum computers grow in the number of qubits, their computational capacity will increase exponentially. This means that algorithms proven to work on a small scale can be applied to larger datasets as the technology advances, paving the way for unprecedented innovation in fields such as cryptography, drug discovery, and artificial intelligence.

The growth of quantum computing is poised to have a transformative impact on society, and staying informed about developments in this field will be crucial for those looking to harness its potential.

Conclusion

The next decade will be crucial for the development of quantum computing. As the technology matures, we can expect to see more practical applications emerge, transforming industries and solving problems that were previously thought to be unsolvable.

While there are still hurdles to overcome, the future of quantum computing is undoubtedly bright, and it will be fascinating to see how it shapes the world of tomorrow.

00Blog views

375

share this blog
user profile avatar

tech girl

Published on • 🕑6 min


A Detailed Guide on How to Create Ruby on Rails Application (API)

If you know me well, I am that ruby girl that enjoys working with ruby on rails. As a language designed to make developers happy, rails surely offer alot of flexibility to developers, including some generators that can generate anything with just one line of code! This guide will discuss how to create a ruby on rails api, assuming you have rails installed.

Ruby on Rails API refers to the application programming interface (API) framework provided by the Ruby on Rails (Rails) web application framework. It allows developers to build and expose APIs for their web applications efficiently.

Why Choose Rails for API Development?

1. ActiveRecord ORM

Rails includes ActiveRecord, a robust Object-Relational Mapping (ORM) system that simplifies database interactions. It allows you to work with databases using Ruby objects and eliminates the need to write complex SQL queries for most operations.

2. Convention Over Configuration

Rails is built around the principle of "Convention Over Configuration," meaning it provides sensible defaults, reducing the decision-making and configuration developers need to do.

3. Rich Ecosystem of Gems

Rails has a vast ecosystem of gems (libraries) that extend its functionality. Whether you need authentication, background processing, or payment integration, there’s likely a gem that fits your needs.

4. Scaffolding

Rails provides generators that can create models, controllers, views, and even tests for you. This feature helps to quickly spin up resources, allowing you to focus more on building the core functionality of your application.

5. Built-in Security Features

Rails includes many security features, such as protection against SQL injection, Cross-Site Scripting (XSS), and Cross-Site Request Forgery (CSRF).

Step-by-Step Guide to Building an API

For this tutorial, we are going to build a simple ruby API for Tesla that returns a list of their vehicle models with the model name, year of manufacture, picture, and price.

Step 1: Create a New Rails Project

Assuming you have installed ruby on rails, open the terminal and run:

rails new tesla --api --skip-test-unit && cd tesla

If you do not have rails installed, see here how to install or run the following command:

gem install rails

2. Generating the Vehicle Model and Controller

Next, we need to leverage the power of rails to scaffold models and get started quicker. Rails come with different commands, which you can view by running rails --help. One of those commands is rails generate which uses rails generator to create assets model routes channel controller generator based on existing templates.

We will create our model, controller, and routes using the rails generator to make things easier.

rails generate scaffold Vehicle name:string year_of_manufacture:integer price:decimal picture:string --no-test-framework

This command will create:

  • A Vehicle model with attributes: name, year_of_manufacture, price, and picture.
  • A VehiclesController with all the standard RESTful actions.
  • The necessary routes for the vehicles resource

3. Running Migrations

Out of the box, rails works with SQLite3 database and thus you need to ensure you have it installed or provide an alternative database such as Postgres when setting up the app. You can also edit the database.yaml file to add your database URL. For this scenario, we will work with the default database.

We need to use rails:migrate to run migrations for our database using activerecord. Rails will automatically create a database for you if it does not exist and create vehiclestable with the specified columns.

rails db:migrate

Step 4: Creating Seed Data 🌱 (Optional)

To populate your database with some initial data, you can create a seed file. Rails provides a convenient way to seed the database with data you can use for testing or development. To add data, open db/seeds.rb. You’ll see that there are already some examples there, but we’ll need to delete these and add our own:

Here is a sample seed data:

Vehicle.create!([
  { name: 'Tesla Model S', price: '51885.17', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MT337,$PPSW,$W40B,$IBB1&view=STUD_FRONT34&model=m3&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year_of_manufacture: 2012 },
  { name: 'Tesla Model 3', price: '100990', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MTS10,$PPSW,$WS90,$IBE00&view=FRONT34&model=ms&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year_of_manufacture: 2017 },
  { name: 'Tesla Model X', price: '120990', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MTX10,$PPSW,$WX00,$IBE00&view=FRONT34&model=mx&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year_of_manufacture: 2015 },
  { name: 'Tesla Model Y', price: '65000', picture: 'https://static-assets.tesla.com/configurator/compositor?&options=$MTY07,$PPSW,$WY19B,$INPB0&view=FRONT34&model=my&size=1920&bkba_opt=2&version=v0028d202109300916&crop=0,0,0,0&version=v0028d202109300916', year_of_manufacture: 2020 }
])

Then, run the seed file to populate your database:

rails db:seed

Step 5: Customizing the Controller

The rails generate command automatically generates a controller with all the methods. However, we need some additional modifications to rescue from activerecord errors and also restrict the types of params we accept when creating a new vehicle.

Navigate to /app/controllers/vehicle_controller.rb and add the following code:

class VehiclesController < ApplicationController
  rescue_from ActiveRecord::RecordNotFound, with: :render_not_found
  rescue_from ActiveRecord::RecordInvalid, with: :render_unprocessable_entity
  # GET /vehicles
  def index
    @vehicles = Vehicle.all
    if params[:name].present?
      @vehicles = @vehicles.where(name: params[:name])
    end
    if params[:year_of_manufacture].present?
      @vehicles = @vehicles.where(year_of_manufacture: params[:year_of_manufacture])
    end
    if params[:price].present?
      begin
        @vehicles = @vehicles.where(price: params[:price])
      rescue ArgumentError
        render json: { error: 'Invalid price parameter' }, status: :bad_request
        return
      end
    end
    render json: @vehicles
  end
  # GET /vehicles/:id
  def show
    @vehicle = Vehicle.find(params[:id])
    render json: @vehicle
  end
  # POST /vehicles
  def create
    @vehicle = Vehicle.create!(vehicle_params)
    render json: @vehicle, status: :created
  end
  # PATCH/PUT /vehicles/:id
  def update
    @vehicle = Vehicle.find(params[:id])
    @vehicle.update!(vehicle_params)
    render json: @vehicle
  end
  # DELETE /vehicles/:id
  def destroy
    @vehicle = Vehicle.find(params[:id])
    @vehicle.destroy!
    head :no_content
  end
  private
  def vehicle_params
    params.require(:vehicle).permit(:name, :year_of_manufacture, :price, :picture)
  end
  def render_not_found
    render json: { errors: ['Vehicle not found'] }, status: :not_found
  end
  def render_unprocessable_entity(invalid)
    render json: { errors: invalid.record.errors }, status: :unprocessable_entity
  end
end

This sample has been modified with extra safety and error handling when a record is not found or cannot be created. The index action has also been updated to handle query parameters for filtering the results. It also includes error handling for invalid price parameters.

Step 5: Start the Server and Get the Data

While you could have started the server earlier on by running rails s, the page would only show the welcome page for rails. Now since we have data, start the server and go to /vehicles to make a get request and return the list of vehicles.

rails s
#test with curl
curl http://localhost:3000/vehicles

You can also test the API using postman or thunderbird extension in vs code for the get, create, update and delete commands. Furthermore, you can debug or even create new vehicles in the database in the rails console by running rails c. 

ruby-on-rails image

Step 6: Modifying Routes

Rails can display a list of all created routes if you run rails routes, which is useful for tracking down routing problems in your app, or giving you a good overview of the URLs in an app you're trying to get familiar with.

You also have the freedom of renaming your routes any way you like, for example instead of the create route for users, you can rename the route signup and then map it to the users create controller. We can do this by modifying the config/routes.rb file.

Here is an Example:

Rails.application.routes.draw do
  resources :vehicles
  resources :users
  post '/signup', to: 'users#create'
  # Defines the root path route ("/")
end

Step 7: Serializing data

We all know it is annoying to return irrelevant data such as created_at_date and updated_at_date in API requests unless we need such data. Luckily, ActiveModel::Serializer allows us to state which data we want to return in our API requests, thus making the response time even faster.

To use the gem, we need to add it to the gem file and run bundle install. The bundle install command in ruby on rails projects is used to install the necessary gems (ruby libraries or packages) specified in the project’s Gemfile.

gem 'active_model_serializers'
bundle install

Next, we need to generate a serializer for our desired model, which in this case is the vehicle model. Remeber to use Capitalization while referring to a model and lowercase when referring to the table. For example, the model is named Vehicle while its associated table in the database is named vehicle. 

rails generate serializer Vehicle

This will create a file app/serializers/vehicle_serializer.rb. Update this file to specify the attributes you want to include in the JSON response:

class VehicleSerializer < ActiveModel::Serializer
  attributes :id, :name, :year_of_manufacture, :price, :picture
end

Conclusion

With Rails, creating a RESTful API is both efficient and enjoyable. Its built-in features like ActiveRecord, the rich ecosystem of gems, and the convention-over-configuration philosophy make Rails a powerful tool for building APIs. In this guide, we’ve built a simple Tesla API, showcasing how easy it is to get up and running with Rails.

There is much more about rails that I have not covered here but this can get you going in no time. Hope you enjoyed this tutorial.

Resources

  1. Getting started with ruby on rails [external link]
  2. Ruby on Rails API tutorial [youtube]
11Blog views

465

share this blog
user profile avatar

Tech Wizard

Published on • 🕑6 min


7 CSS Mistakes that Beginners Often Make

CSS is not as simple as it looks and developers often make some mistakes that leave them struggling with what to do. CSS is perceived as an unintuitive and difficult language to work with because of these common mistakes that thwart most developers when they try to write CSS. As a result, most developers opt to use CSS frameworks such as Bootstrap and Tailwind CSS to avoid writing their own CSS.

In this blog, we will discuss five common mistakes that developers often make. Recognizing and avoiding these mistakes will help you write CSS that:

  1. Works across devices—not just your laptop
  2. Works the first time you try it
  3. It makes you less frustrated with CSS

Let's dive in.

#Mistake 1: Not using CSS Reset

This is one of my surprising discoveries and I have only realized that I have been doing CSS wrong all this time. Browsers have default styles that serve as a fallback if the stylesheet does not exist. However, these default styles are different across browsers. In any case, two browsers rarely provide identical default styling, so the only real way to ensure your styles are effective is to use a CSS reset.

A CSS reset entails resetting (or, rather, setting) all the styles of all the HTML elements to a predictable baseline value. The beauty of this is that once you include a CSS reset effectively, you can style all the elements on your page as if they were all the same to start with.

CSS reset is a blank slate that helps you have consistent styling across different browsers. Most often, this entails setting a margin:0 and padding:0, although there is a need to reset other elements.


* {
  margin: 0;
  padding: 0;
  border: 0;
  outline: 0;
  font-weight: inherit;
  font-style: inherit;
  font-size: 100%;
  font-family: inherit;
  vertical-align: baseline;
}
/* remember to define focus styles! */
:focus {
  outline: 0;
}
body {
  line-height: 1;
  color: black;
  background: white;
}
ol,
ul {
  list-style: none;
}
/* tables still need 'cellspacing="0"' in the markup */
table {
  border-collapse: separate;
  border-spacing: 0;
}
caption,
th,
td {
  text-align: left;
  font-weight: normal;
}
blockquote::before,
blockquote::after,
q::before,
q::after {
  content: "";
}
blockquote,
q {
  quotes: "" "";
}

#Mistake 2: Using px Units

I am also actually guilty of using px units for font-sizes, margins, padding and height or weight attributes. While using px units can be fine in some instances, overreliance on them leads to accessibility issues.

According to MDN,  Defining font sizes in px is not accessible, because the user cannot change the font size in some browsers. For example, users with limited vision may wish to set the font size much larger than the size chosen by a web designer. Avoid using them for font sizes if you wish to create an inclusive design.

However, px units are also bad for setting height and width for content, since content might also overflow if the user increases the default font size in the browser. Using px units in-media queries also affects the layout when a user zooms in or changes the default font size. 

đźš« Mistake

p {
  font-size: 16px;
 /*this prevents users from resizing the font-size*/
  line-height: 20px;
  margin-bottom: 8px;
}

âś… Correct

body {
  font-size: 16px;
}
p {
  font-size: 1rem;
  line-height: 1.25;
  margin-bottom: 0.5em;
}

#Mistake 3: Using IDs as Selectors

One of the most overlooked problems in web development is the use of over-qualifying selectors that are too specific and difficult to override. IDs selectors have more specificity in CSS, meaning you cannot override them or reuse the style in another component.

Always write your CSS selectors with the very minimum level of specificity necessary for it to work. Including all that extra fluff may make it look safer and more precise, but when it comes to CSS selectors, there are only two levels of specificity: specific, and not specific enough.

đźš« Mistake

#header {
  font-size: 1em;
  line-height: normal;
}

âś… Correct

.header {
  font-size: 1em;
  line-height: normal;
}

In general, you should avoid using overly specific selectors in CSS. The CSS Specificity Tournament illustrates why it's a bad idea to use selectors that are too powerful. When a selector is very powerful in the tournament, it wins quickly and early on, which means the only way to beat it is to write an even more powerful selector.

 

This tendency for specificity to always escalate is known as a specificity war. Similar to stockpiling nuclear weapons, no one wins in this war—things only become more difficult to de-escalate as specificity increases. The best way to avoid a full-blown specificity war is to not use highly specific selectors in the first place.

#Mistake-4: Named Colors

Another mistake that I discovered when doing my research is the problem with named colors. Developers often ignore that what you perceive to be a specific color looks very different across browsers.

By saying: color: red; You’re essentially saying that the browser should display what it thinks red is. If you’ve learned anything from making stuff function correctly in all browsers it’s that you should never let the browser decide how to display your web pages.

Instead, you should go to the effort to find the actual hex value for the color you’re trying to use. That way, you can make sure it’s the same color displayed across all browsers. You can use a color cheatsheet that provides a preview and the hex value of a color.

đźš« Mistake

.header {
  font-size: 1em;
  line-height: normal;
  color: red;
}

âś… Correct

.header {
  font-size: 1em;
  line-height: normal;
  color: rgb(255, 0, 0);
}

#Mistake 5: Not Using Shorthand Properties

As a developer, one rule is to never repeat yourself. Therefore, you should find ways to minimize the number of lines of code that you write.

One common problem with CSS is understanding shorthand properties for things like margin, padding and inset. As a confession, I also struggle with this problem and often have to look to the documentation on whether margin: 0 5px sets the margin in top-bottom or left-right.

đźš« Mistake

.my-cool-div {
  margin-top: 50px;
  margin-right: 0;
  margin-bottom: 50px;
  margin-left: 0;
  background-image: url(background.png);
  background-repeat: repeat-y;
  background-position: center top;
}

âś… Correct

.my-cool-div {
  margin: 50px 0;
  background: url(background.png) repeat-y center top;
}

Using shorthand CSS improves efficiency and makes it easier to maintain our code. However, this could take time to master and I recommend checking the documentation.

#Mistake 6: Overreliance on Position Absolute

Position absolute is that one band-aid solution that can cause more problems as it breaks the document flow. When using positions absolute, MDN recommends that you ensure that elements that are positioned with an absolute or fixed value do not obscure other content when the page is zoomed to increase text size.

Position absolute should be the last choice since it has some effects such as pulling the element out of the flow and making it stack over other things. 

Furthermore, elements positioned absolutely don't naturally adapt to changes in screen size or parent element dimensions, which can break the layout on different devices.

đźš« Mistake

.sidebar {
  position: absolute;
  top: 50px;
  right: 0;
  width: 30%;
  background-color: #e0e0e0;
  padding: 20px;
}

âś… Correct

.sidebar {
  transform: translateY(50px) translateX(0);
  /* Moves the element down by 50px */
  width: 30%;
  background-color: #e0e0e0;
  padding: 20px;
}

In this example, we see that we can achieve the same functionality without breaking the document flow by using transform to move the sidebar down by 50px.

#Mistake 7: Collapsing Margins

Collapsing margins can be really hard to understand and frustrating to decode since you might not understand why your applied margin or padding is not working as expected.

The top and bottom margins of blocks are sometimes combined (collapsed) into a single margin whose size is the largest of the individual margins (or just one of them if they are equal), a behavior known as margin collapsing. Note that the margins of floating and absolutely positioned elements never collapse.

Understanding margin collapse is essential for achieving consistent spacing in your layouts, particularly in scenarios where you want to ensure a specific amount of space between elements.

One solution to collapsing margins is using padding, especially for child elements rather than margins. It is generally advised not to add margin to the child element, especially in JavaScript frameworks such as react since this might affect their reusability.

You must always remember that adding a margin to a child element can affect the position of the parent element as the margins collapse.

đźš« Mistake

/* html */
/* 
<div class="element1">Element 1</div>
<div class="element2">Element 2</div> 
*/
.element1 {
  margin-bottom: 20px;
}
.element2 {
  margin-top: 30px;
}
/* the total margin will be 30px rather than 50px */

âś… Correct

.element1 {
  margin-bottom: 20px;
  padding-bottom: 1px;
  /* Prevents collapse */
}
.element2 {
  margin-top: 30px;
}

Conclusion

I hope you enjoyed this article, and that it gave you a sense of how to avoid the top most common mistakes developers make when they write CSS. There are many mistakes not covered in this blog such as separating between layout and content elements, overusing flex box and much more. Comment below with some other mistakes.

01Blog views

590

share this blog